28 ms·
Why Haskell?
- louthy 2y agoI love Haskell the language, but Haskell the ecosystem still has a way to go: * The compiler is slower than most mainstream language compilers * Its ability to effectively report errors is poorer * It tends to have 'first error, breaks rest of compile' problems * I don't mind the more verbose 'stack trace' of errors, but I know juniors/noobs can find that quite overwhelming. * The tooling, although significantly better than it was, is still poor compared to other some other functional languages, and really poor compared to mainstream languages like C# * This ^ significantly steepens the learning curve for juniors and those new to Haskell and generally gets in the way for those more experienced. * The library ecosystem for key capabilities in 'enterprise dev' is poor. Many unmaintained, substandard, or incomplete implementations. Often trying their best to be academically interesting, but not necessarily usable. The library ecosystem is probably the biggest issue. Because it's not something you can easily overcome without a lot of effort. I used to be very bullish on Haskell and brought it into my company for a greenfield project. The company had already been using pure-FP techniques (functors, monads, etc.), so it wasn't a stretch. We ran a weekly book club studying Haskell to help out the juniors and newbies. So, we really gave it its best chance. After a year of running a team with it, I came to the conclusions above. Everything was much slower -- I kept telling myself that the code would be less brittle, so slower was OK -- but in reality it sapped momentum from the team. I think Haskell's biggest contribution to the wider community is its ideas, which have influenced many other languages. I'm not sure it will ever have its moment in the sun unfortunately.
- Vosporos 2y agoIf you are willing / able to report these pain points in detail to the Haskell Foundation, this is going to be valuable feedback that will help orient the investments in tooling in the near future.
- louthy 2y agoI think tooling is something that is clearly on a good trajectory. When I consider what the Haskell tooling was like when I first started using it, well, it was non-existent! (and Cabal didn't even understand what dependencies were, haha!) So, it's much, much better than it was. It's still not comparable to mainstream languages, but it's going the right way. So, I wouldn't necessarily take that as the killer. The biggest issue was the library ecosystem. We spent an not-small amount of time evaluating libraries, realising they were not up to scratch, trying to do build our own, or interacting with the authors to understand the plans. When you're trying to get moving at the start of a project, this can be quite painful. It takes longer to get to an MVP. That's tough when there are eyes on its success or not. Even though I'd been using Haskell for at least a decade before we embarked upon that path, I hadn't really ever built anything substantial. The greenfield project was a complex beast on a number of levels (which was one of the reasons I felt Haskell would excel, it would force us to be more robust with our architecture). But, we just couldn't find the libraries that were good enough. My sense was there's a lot of academics writing libraries. I'm not implying that academics write poor code; just that their motivations aren't always aligned with what an industry dev might want. Usually this is around simplicity and ease-of-use. And, because quite a lot of libraries were either poorly documented or their intent was impenetrable, it would take longer to evaluate. I think if the Haskell Foundation are going to do anything, then they should probably write down the top 50 needed packages in industry, and then put some funding/effort towards helping the authors of existing libraries to bring them up to scratch (or potentially, developing their own), perhaps even create a 'mainstream adoption style guide', that standardises the library surfaces -- there's far too much variability. It needs a keen eye on what your average industry dev needs though. I realise there are plenty of companies using Haskell successfully, so this should only be one data point. But, it is a data point of someone who is a massive Haskell (language) fan. Haskell has had a massive influence on me and how I write code. It's directly influenced a major open-source project I have developed [1]. But, unfortunately, I don't think I'll use it again for a pro project. [1] https://github.com/louthy/language-ext https://github.com/louthy/language-ext
- deleted 2y ago[deleted]
- ngalaiko 2y ago[dead]
- mhitza 2y ago> * It tends to have 'first error, breaks rest of compile' problems `-fdefer-type-errors` will report those errors as warnings and fail at runtime, which is good when writing/refactoring code. Even better the Haskell LSP does this out of the box. > * The tooling, although significantly better than it was, is still poor compared to other some other functional languages, and really poor compared to mainstream languages like C# Which other functional programming languages do you think have better tooling? Experimenting lately with OCaml, feels like Haskell's tooling is more mature, though OCaml's LSP starts up faster, almost instantly.
- louthy 2y ago> Which other functional programming languages do you think have better tooling? F#, Scala > OCaml's LSP starts up faster It was two years ago that I used Haskell last and the LSP was often crashing. But in general there were always lots of 'niggles' with all parts of the tool-chain that just killed developer flow. As I state in a sibling comment, the tooling is on the right trajectory, it just isn't there yet. So, this isn't the main reason to not do Haskell.
- mhitza 2y agoCoincidentally I've started using the Haskell LSP around two years ago, and crashing is not one of the issues I've had with it. Since you mention F#, and C# in your previous comment, are you on the Windows platform? Maybe our experience different because of platform as well. Using GHCup to keep in sync compatible versions of GHC, Cabal and the LSP probably contributed a lot to the consistent feel of the tooling. I use the Haskell LSP for its autocompletion, reporting compile errors in my editor, and highlighting of types under cursor. There are still shortcomings with it that are annoyances: * When I open up vim, it takes a good 5-10 seconds (if not a bit more) until the LSP is finally running. * When a new dependency is added to the cabal file, the LSP needs to be restarted (usually I quit vim and reopen the project). * Still no support for goto definition for external libraries. The workaround I have to use in this case is to `cabal get dependency-version` in a gitignored directory and use hasktags to keep a tags file to jump to those definitions and read the source code/comments. The later two have open GitHub issues, so at least I know they will get solved at some point.
- moomin 2y agoNo lies detected. I love Haskell, but productivity is a function of the entire ecosystem, and it’s just not there compared to most mainstream languages.
- gtf21 2y ago> The library ecosystem is probably the biggest issue. I'd love to know which things specifically you're thinking about. For what we've been building, the "integration" libraries for postgres, AWS, etc. have been fine for us, likewise HTTP libraries (e.g. Servant) have been great. I haven't _yet_ encountered a library problem, so am just very curious.
- chii 2y agoProbably referring to something like spring (for java), which is a one stop shop for everything, including things like integration with monitoring/analytics, rate-limiting, etc
- okkdev 2y agoSpring is probably the worst framework created, so I wouldn't list that as an example :/
- louthy 2y agoThe project was a cloud agnostic platform-as-a-service for building healthcare applications. It needed graph-DBs, Postgres, all clouds, localisation, event-streams, UIs, etc. I won't list where the problems were, because I don't think it's helpful -- each project has its own needs, you may well be lucky where we were not. Certainly the project wasn't a standard enterprise app, it was much more complex, so we had some foundational things we needed that perhaps your average dev doesn't need. However, other ecosystems would usually have a decent off-the-shelf versions, because they're more mature/evolved. You have to realise that none of the problems were insurmountable, I had a talented team who could overcome any of the issues, it just became like walking through treacle trying to get moving. And yes, Servant was great, we used that also. Although we didn't get super far in testing its range.
- crote 2y agoA few years ago I tried to use Servant to make a CAS[0] implementation for an academic project. One issue I ran into was that Servant didn't have a proper way of overriding content negotiation: the CAS protocol specified a "?format=json" / "?format=xml" parameter, but Servant had no proper way of overriding its automatic content negotiation - which is baked deeply into its type system. I believe at the time I came across an ancient bug report which concluded that it was an "open research question" which would require "probably a complete rework". Another issue was that Servant doesn't have proper integrated error handling. The library is designed around returning a 200 response, and provides a lot of tooling to make that easy and safe. However, I noticed that at the time its design essentially completely ignored failures! Your best option was basically a `Maybe SomeResponseType` which in the `None` case gave a 200 response with a "{'status': 'error'}" content. There was a similar years-old bug report for this issue, which is quite worrying considering it's not exactly rocket science, and pretty much every single web developer is going to run into it. All of this gave a feeling of a very rough and unfinished library, whose author was more concerned about writing a research paper than actually making useful software. Luckily those issues had no real-world implication for me, as I was only a student losing a few days on some minor project. But if I were to come across this during professional software development I'd be seriously pissed, and probably write off the entire ecosystem: if this is what I can expect from "great" libraries, what does the average ones look like - am I going to have to write every single trivial thing from scratch? I really love the core language of Haskell, but after running into issues like these a few dozen times I unfortunately have trouble justifying using it to myself. Maybe Haskell will be great five or ten years from now, but in its current state I fear it is probably best to use something else. [0]: https://en.wikipedia.org/wiki/Central_Authentication_Service https://en.wikipedia.org/wiki/Central_Authentication_Service
- catgary 2y agoI kind of agree that Haskell missed its window, and a big part of the problem is the academic-heavy ecosystem (everyone is doing great work, but there is a difference between academic and industrial code). I’m personally quite interested in the Koka language. It has some novel ideas (functional-but-in-place algorithms, effect-handler-aware compilation, it uses reference counting rather than garbage collection) and is a Microsoft Research project. It’s starting to look more and more like an actual production-ready language. I can daydream about Microsoft throwing support behind it, along with some tooling to add some sort of Koka-Rust interoperability.
- the_duke 2y agoKoka is indeed incredibly cool, but: It sees sporadic bursts of activity, probably when an intern is working on it, and otherwise remains mostly dormant. There is no package manager that could facilitate ecosystem growth. There is no effort to market and popularize it. I believe it is fated to remain a research language indefinitely.
- catgary 2y agoYou’re probably right. I just think it’s the only real candidate for a functional language that could enter the zeitgeist like Rust or Swift did, it’s a research language that has been percolating at Microsoft for some time. A new language requires a major company’s support, and they should build an industry-grade ecosystem for at least one problem domain.
- pyrale 2y agoMost of your comments boil down to two items: - The Haskell ecosystem doesn't have the budget of languages like Java or C# to build its tooling. - The haskell ecosystem was innovative 20 years ago, but some newer languages like Rust or Elm have much better ergonomics due to learning from their forebearers. Yes, it's true. And it's true for almost any smaller language out there.
- troupo 2y agoCounterpoint: Elixir. While it sits on top of industrial-grade Erlang VM, the language itself produced a huge ecosystem of pragmatic and useful tools and libraries.
- louthy 2y agoIf you boil down my comments, sure, you could say that. But, that's why I didn't boil down my comments and used more words, because ultimately, it doesn't say that. The thread is "Why Haskell?", I'm offering a counterpoint based on experience. YMMV and that's fine.
- devjab 2y ago> compared to mainstream languages like C# Out of curiosity does this also hold true for F#?
- louthy 2y agoF#’s tooling is worse than C# for sure, but it’s a big step-up from Haskell and has access to the .NET framework. I listed C# because that’s the mainstream language I know the best, and arguably has best-in-class tooling. Of course you have to be prepared to lose some of the creature comforts when using a more left-field language. But, you still need to be productive. The whole ecosystem has to be a net gain in productivity, or stability, or security, or maintainability — pick your poison depending on what matters to your situation. I had hoped Haskell would pay dividends due to its purity, expressive type-system, battle tested-ness, etc. I expected us to be slower, just not as slow as it turned out. Ultimately the trade off didn’t work for us.
- devjab 2y agoThank you for the answer. It’s exactly because of C#’s excellent tooling I was wondering if they had done similar for F#. > The whole ecosystem has to be a net gain in productivity, or stability, or security, or maintainability — pick your poison depending on what matters to your situation. I very much agree with you on this. I’ve worked in places where we used Typescript on the back-end because it was easier for a small team to work together (and go on vacations) while working in the same language even though there was a trade off performance wise. Ultimately I think it’s always about finding the best way to be productive.
- louthy 2y agoF#'s biggest issue is C#. It benefits from Visual Studio and Jetbrains Rider as best-in-class tools, but having to rely on the .NET Framework means relying on an OO first library ecosystem in your functional code. Which can be clumsy and looks a little messy with the mix of camelCase and PascalCase functions. Also, it has support for features that probably shouldn't be in the language, but are because of C# (interfaces and type-inheritance for example). The compiler is slower than C# but arguably fast enough. And they have made a weird choice about ordering of source-files dictating ordering of compilation, so you have to manually sort source-files in the IDE. Which is both a pain and makes it sometimes hard to visually find your source-file because they're not in alphabetical order. I like F# but it doesn't have enough unique features over C# to make it worthwhile imho. Disclaimer: The last time I wrote any F# was about 5 years ago. Things may be different now!
- ants_everywhere 2y agoI completely agree. I'm interested in making the Haskell tooling system better. I would welcome anyone with Haskell experience to let me know what you think would be the highest priority items here. I'm also curious about the slowness of compilation and whether that's intrinsic to the design of GHC.
- tome 2y ago> I would welcome anyone with Haskell experience to let me know what you think would be the highest priority items here. Simplifying cabal probably, though that's a system-level problem, just just a cabal codebase problem.
- ants_everywhere 2y agothanks!
- lemonwaterlime 2y agoThe Brittany code fixer needs a maintainer. The previous one had to step away. It has a unique approach to code formatting that the ormolu/fourmolu formatters doesn’t. There’s lots of the philosophy and such in the docs. I like it better than the ormolu family because it respects your placement of comments and just formats the code itself. But it isn’t maintained as of a few years ago. https://hackage.haskell.org/package/brittany https://hackage.haskell.org/package/brittany
- ants_everywhere 2y agothanks!
- lemonwaterlime 2y agoGreat vids by the way! I watch your channel.
- louthy 2y agoIndeed, we used Brittany too, and I was pretty gutted when the support ended!
- robocat 2y agoIt is a shame that the article almost completely ignores the issue of the tooling. I particularly find the attitude in the following paragraph offensively academically true: All mainstream, general purpose programming languages are (basically) Turing-complete, and therefore any programme you can write in one you can, in fact, write in another. There is a computational equivalence between them. The main differences are instead in the expressiveness of the languages, the guardrails they give you, and their performance characteristics (although this is possibly more of a runtime/compiler implementation question). I decided to have a go at learning the basics of Haskell and the first error I got immediately phased me because it reminded me of unhelpful compilers of the 80s. I have bashed my head against different languages and poor tooling enough times to know I can learn, but I've also done it enough times that I am unwilling to masochistically force myself through that gauntlet unless I have a very good reason to do so. The "joy" of learning is absent with unfriendly tools. The syntax summary in the article is really good. Short and clear.
- gtf21 2y ago> It is a shame that the article almost completely ignores the issue of the tooling. Mostly because while I found of the tooling occasionally difficult, I didn’t find Haskell particularly bad compared to other language ecosystems I’ve played with, with the exception of Rust, for which the compiler errors are really good. > The syntax summary in the article is really good Thanks, I wasn’t so sure how to balance that bit.
- samatman 2y ago> All mainstream, general purpose programming languages are (basically) Turing-complete, and therefore any programme you can write in one you can, in fact, write in another. That stuck out to me as well, I said out loud "that is a very Haskell thing to say". It would be more accurate to say that Turing Completeness means that any programme you write in one language, may be run in another language by writing an emulator for the first programme's runtime, and executing the first programme in the second. Because it is not "in fact" the case that a given developer can write a programme in language B just because that developer can write the program in language A. It isn't even "in principle" the case, computability and programming just aren't that closely related, it's like saying anything you can do with a chainsaw you can do with a pocketknife because they're both Sharp Complete. I shook it off and enjoyed the rest of the article, though. Haskell will never be my jam but I like reading people sing the virtues of what they love.
- RandomThoughts3 2y agoThe Haskell community is also very opinionated when it comes to style and some of the choices are not to everyone taste. I’m mostly thinking of point-free being seen as an ideal and the liberal usage of operators.
- ParetoOptimal 2y agoPoint-free and liberal use of operators are and have long been minority viewpoints in Haskell. I say this as someone who prefers symbols, point free, and highly appreciates "notation as a tool of thought".
- RandomThoughts3 2y agoFor a minority viewpoint, it seems fairly pervasive in the library ecosystem at least to me. Then again, I hate most usage of point-free and I think the correct amount of custom operator is exactly zero so I might be particularly sensitive to it.
- tome 2y ago> It tends to have 'first error, breaks rest of compile' problems Sort of. It has a "failure at a stage prevents progress to next stage", so a parse error means you won't type check (or indeed, continue parsing). See these proposals for some progress on the matter * https://github.com/haskellfoundation/tech-proposals/pull/63 https://github.com/haskellfoundation/tech-proposals/pull/63 * https://github.com/ghc-proposals/ghc-proposals/pull/333 https://github.com/ghc-proposals/ghc-proposals/pull/333
- louthy 2y agoI understand why it happens, but it's an absolute killer for refactoring. I didn't mention refactoring in my list because it may just be personal experience: my style of coding is to write fearlessly knowing that I will also refactor fearlessly. So less upfront thinking, more brute force writing (on instinct) & aggressive refactoring. I find I get my solution much faster and it ends up being more elegant. Having a parse error or a type inference error in another module causing all other inference to fail kills the refactoring part of that process where there are syntax/semantic errors everywhere for a period of time whilst I fix them up. It's good to see the issue acknowledged and hopefully resolved in the future. Additionally, it would be good to see some proper refactoring tooling. Renaming, moving types/functions from one module to another, etc.
- tome 2y ago> Additionally, it would be good to see some proper refactoring tooling. Renaming, moving types/functions from one module to another, etc. Doesn't HLS have renaming, at least?
- nh2 2y ago> The compiler is slower than most mainstream language compilers Depends on which mainstream languages one compares with; there's always C++. My project here has 50k lines Haskell, 10k C++, 50k lines TypeScript (code-only, not comments). Counting user CPU time (1 core, Xeon E5-1650 v3 3.50GHz): TypeScript 123 lines/s Haskell 33 lines/s C++ 7 lines/s
- Liquid_Fire 2y agoCan you clarify what "7 lines/s" means? Surely you are not saying that your 10k lines of C++ take more than 23 minutes to compile on a single core? Is it 10k lines of template spaghetti? For comparison, I just compiled a 25k line .cpp (probably upwards of 100k once you add all the headers it includes) from a large project, in 15s. Admittedly, on a slightly faster processor - let's call it 30s.
- nh2 2y agoIt means exactly that, 23 mins on a single core for 10k lines! (Insert "This is C++" Sparta image here.) It is quite clean application code but it _uses_ some of the most template heavy open-source libraries around (e.g. Eigen, CGAL, boost) -- all hallmarks of the strength of C++. If you look at other popular open-source C++ projects, such as Ceph or the Point Cloud Library (PCL), 8-hour single-core compile times are, unfortuanately, normal. I fully agree that C++ code bases that are more C-like compile much faster. But many typical C++ projects that use standard C++ features compile at 7 lines per second. The same holds for Haskell: If you write very simple code and do not use common functionality (TemplateHaskell, Generics deriving), you'll also get a 20x compile time speedup.
- andrewstuart 2y agoWhenever I see Haskell stuff, Steve Yegge's famous 2010 blog post instantly comes to mind: "Haskell Researchers Announce Discovery of Industry Programmer Who Gives a Shit" http://steve-yegge.blogspot.com/2010/12/haskell-researchers-announce-discovery.html http://steve-yegge.blogspot.com/2010/12/haskell-researchers-...
- TacticalCoder 2y agoIt's a classic and Steve Yegge hasn't been nice to Haskell with that one. As a Java programmer I used to love an even older blog making fun of Java and its ecosystem called, IIRC, "The bile blog". It was trashy, mean, using lots of swear words and it was really good.
- grumpyprole 2y agoAnd he got it completely wrong, as more and more Haskell features end up in industrial programming languages. Oracle's Java architect even publically stated he was influenced by Haskell.
- mg 2y agoTL/DR: Haskell makes you add more meta information to the code, so that compilers can reason about it. One of the examples in the article: Python: def do_something(): result = get_result() if result != "a result": raise InvalidResultError(result) return 42 Haskell: doSomething :: Either InvalidResultError Int doSomething = let result = getResult in if result /= "a result" then Left (InvalidResultError result) else Right 42 Personally, I prefer the Python version. In my experience, the benefits of adding meta information like types and possible return values are very small. Because the vast majority of time fixing problems in software development is spent on systematic bugs and issues, not on dealing with type errors.
- catgary 2y agoOk, your Haskell example is basically drawing your opponent in the wojack meme and declaring yourself the winner.
- black_knight 2y agoThe point is that the Haskell type system is an expressive way of solving systematic bugs. You can express both the data itself and the valid ways of handling it using types, which gives you a space to design yourself out of systemic problems. And when something goes awry, the strict typing and control over side effects means that you can refactor fearlessly! Re. the example, the compiler can infer types, and you can write almost the exact same code in Haskell as in Python: doSomething = do let result = getResult when (result /= "a result") (throwError $ InvalidResultError result) return 42 But as it has been noted, you are unlikely to find code like this in Haskell. An element of Either type at the top level, without arguments is either always going to be Left or always going to be Right, so it is a bit pointless. Since this example is so abstract it is hard to see what the idiomatic Haskell would be, because it would depend on the particulars.
- gtf21 2y ago> so that compilers can reason about it Actually this is the wrong takeaway, I think it's so that programmers can reason about it. This isn't about type errors, it's about precisely describing a particular computational expression. In the python example, it's very unclear what `do_something` actually _does_.
- cpa 2y agoHaskell has had a profound impact on the way I think about programming and how I architect my code and build services. The stateless nature of Haskell is something that many rediscover at different points in their careers. Eg in webdev, it's mostly about offloading state to the database and treating the application as "dumb nodes." That's what most K8s deployments do. The type system in Haskell, particularly union types, is incredibly powerful, easy to understand for the most part (you don't need to understand monads that deeply to use them), and highly useful. And I've had a lot of fun micro-optimizing Haskell code for Project Euler problems when I was studying. Give it a try. Especially, if you don't know what to expect, I can guarantee that you'll be surprised! Granted, the tooling is sh*t.
- adastra22 2y agoHaskell also changed the way I think about programming. But I wonder if it would have as much of an impact on someone coming from a language like Rust or even modern C++ which has adopted many of haskell’s features?
- mmoll 2y agoTrue. I often think of Rust as a best-of compilation of Haskell and C++ (although I read somewhere that OCaml had a greater influence on it, but I don’t know that language well enough) In real life, I find that Haskell suffers from trying too hard to use the most general concept that‘s applicable (no pun intended). Haskell programs happily use “Either Err Val” and “Left x” where other languages would use the more expressive but less general “Result Err Val” and “Error x”. Also, I don’t want to mentally parse nested liftM2s or learn the 5th effect system ;-)
- hollerith 2y ago>I read somewhere that OCaml had a greater influence on it Whoever wrote that is wrong.
- deleted 2y ago[deleted]
- scotty79 2y ago> Unlearning and relearning Interesting how, when encountering Rust, I didn't have to unlearn reference based programming, just learn value based programming. Somehow pure functional language advocates insist you are doing things wrong and need to unlearn it first. Kind of reminds me of a sect that promises you great things if only you work hard on leaving all your prior life behind.
- gtf21 2y agoI think there are more fundamental differences between functional and imperative programming paradigms (or, perhaps, declarative and imperative programming styles?) than between passing by reference and passing by value (after all, variables are just references, filesystems have links, it just doesn't seem that unfamiliar). I have definitely seen people struggle to wrap their head around declaring expressions representing what they want to compute when they are very used to imperative control flow like mutating some state while iterating through a loop. > Kind of reminds me of a sect that promises you great things if only you work hard on leaving all your prior life behind. I think this is sort of saying "hey this one thing looks like this other thing I don't like, therefore it must carry all the same problems". Perhaps we can call it "the duck type fallacy", but I don't think it's true to say that "anything which tries to change paradigm" is equivalent to cults.
- chii 2y ago> definitely seen people struggle to wrap their head around... I think it's a top-down vs bottoms-up approach to solving a problem. Most people actually think in terms of top-down. They break a problem down into smaller sub-problems, which they then either break down more or has done sufficient breaking down to solve it. I think functional style of thinking would make you do a bottoms-up approach to problem solving. You will create a very small solution (function) to solve a very trivial version/type of the problem, then repeat for each small thing. Once you have sufficient number of basic functions written, you can assemble them into solving a bigger problem.
- nineteen999 2y ago
- fbn79 2y agoWe need a language with Haskell syntax, Rust memory management and Typescript toolchain/ecosystem :))
- joelthelion 2y agoI'd argue the syntax is the worst part of haskell. In particular, the lack of object notation for accessing fields (e.g. car.doors) is particularly frustrating. I still love the language, BTW.
- gtf21 2y agoYou can have that syntax if you want it via `OverloadedRecordDot`. I actually really like the syntax as it makes it easy to write DSLs which are actually just Haskell functions.
- rebeccaskinner 2y agoThere is an extension that lets you do this now (OverloadedRecordDotSyntax) but truthfully I think it’s a really bad idea. The (.) operator already has a very concrete meaning in Haskell, and record dot notation means that you suddenly need to care about the specific details of how values are calculated. Field accessor functions are much better imo even if they seem a little odd.
- swiftcoder 2y agoI feel like you've specifically picked the worst part of each language here. Haskell's syntax, like many FP syntaxes, is inscrutable on first acquaintance. There's a reason hybrid languages like Elixir thrive... Rust's memory management is a great boon for a close-to-the-metal language, but if all your types are immutable you don't actually need/want to deal with the borrow checker. Typescripts toolchain and ecosystem are... ok, at best? I'd give a solid pitch for the Rust ecosystem having reproduced the best parts thereof (and there is still room for improvement even so)
- fbn79 2y agoHaskell have a garbage collector (and it have a lot or work to do). So even if you are in the context of immutability, if you don't want a gc, you still need to take care of memory yourself using RAII like Rust or any other low level technique. About syntax for me haskell is beautiful. But maybe it's just my love for having type notation aside from function declaration and not mixed.
- Mikhail_K 2y agoHaskell programs are hard to read and hard to reason about. That is the reason the language is not very practical. The promise "if Haskell program compiles, it is probably correct" have not materialized. The most popular Haskell project Pandoc has 1000 open issues.
- grumpyprole 2y agoThis is a poor and lazy criticism of Haskell. It might be hard to reason about the memory usage and other operational behaviours of a Haskell program, but the ability to reason about semantics and correctness is far ahead of the mainstream. It actually supports equational reasoning. It has statically checked effect tracking, checked encapsulation of mutable state and much more. There is no all powerful pervasive "ambient monad" that lets code do absolutely anything.
- Mikhail_K 2y ago> It might be hard to reason about the memory usage and other operational behaviours of a > Haskell program, but the ability to reason about semantics and correctness is far ahead > of the mainstream. For any practical program, memory usage and number of operations are part of the engineering specification and no one will deem correct a program that exceeds those specifications. So you just confirmed “impractical”, “academic” and “niche” charges. > It actually supports equational reasoning. TLDR: to understand what a Haskell 5-liner does, you sometimes have to read a paper. Are you actually disputing “impractical” and “academic” labels, or saying that those _good_ things?
- grumpyprole 2y ago> For any practical program, memory usage and number of operations are part of the engineering specification And yet if I read a C++ program, I have no idea with just a local inspection where, if any, the allocations are happening. Reasoning about operational behaviour is not exactly a solved problem in other languages either. > TLDR: to understand what a Haskell 5-liner does, you sometimes have to read a paper. You have to understand the syntax and the semantics and genuinely know what you are doing. This is no different to any other programming language. It would require a whole paper to explain JavaScripts equality operator! However, Haskell does has one distinct advantage, the abstractions often come from maths and are very widely applicable. These abstractions will still be around in 10 years time.
- bwidlar 2y agoAn implementation of an extended subset of Haskell. It uses combinators for the runtime execution: https://github.com/augustss/MicroHs https://github.com/augustss/MicroHs https://www.youtube.com/watch?app=desktop&v=uMurx1a6Zck&t=36m https://www.youtube.com/watch?app=desktop&v=uMurx1a6Zck&t=36...
- tromp 2y agoAn even more minimal Haskell compiler and combinatory logic runtime won in the 26th IOCCC: https://crypto.stanford.edu/~blynn/compiler/ioccc.htm https://crypto.stanford.edu/~blynn/compiler/ioccc.htm
- Coolbeanstoo 2y agoI would like to use haskell or another functional language professionally. I try them out (ocaml,haskell,clojure,etc) from time to time and think they're fairly interesting, but i struggle to figure out how to make bigger programs with them as I've never seen how you build up a code base with the tools they provide and with someone to review the code i produce and so never have any luck with jobs i've applied to. On the flipside I never had too much trouble figuring out how to make things with Go, as it has so little going on and because it was the first language i worked with professionally for an extended period of time. I think that also leads me to trying to apply the same patterns because I know them even if they dont really work in the world of functional languages Not sure what the point of this comment is, but I think i just want to experience the moment of mind opening-ness that people talk about when it comes to working with these kinds of languages on a really good team
- cosmic_quanta 2y agoI have also initially struggled with structuring Haskell programs. Without knowing anything about what you want to do, here's my general approach: 1. Decide on an effect system Remember, Haskell is pure, so any side-effect will be strictly explicit. What broad capabilities do you want? Usually, you need to access some program-level configuration (e.g. command-line options) and the ability to do IO (networking, reading/writing files, etc), so most people start with that. https://tech.fpcomplete.com/blog/2017/06/readert-design-pattern/ https://tech.fpcomplete.com/blog/2017/06/readert-design-patt... 2. Encode your business logic in functions (purely if possible) Your application does some processing of data. The details don't matter. Use pure functions as much as possible, and factor effectful computations (e.g. database accesses) out into their own functions. 3. Glue everything together in a monadic context Once you have all your business logic, glue everything together in a context with your effect system (usually a monad stack using ReaderT). This is usually where concurrency comes in (e.g. launch 1 thread per request). --- Beyond this, your application design will depend on your use-case. If you are interested, I strongly suggest to read 'Production Haskell' by Matt Parsons, which has many chapters on 'Haskell application structure'.
- solomonb 2y ago
- axilmar 2y agoMy question for Haskellers is how to do updates of values on a large scale, let's say in a simulation. In imperative languages, the program will have a list of entities, and there will be an update() function for each entity that updates its state (position, etc) inline, i.e. new values are overwriten onto old values in memory, invoked at each simulation step. In Haskell, how is that handled? do I have to recreate the list of entities with their changes at every simulation step? does Haskell have a special construct that allows for values to be overwritten, just like in imperative languages? Please don't respond with 'use the IO monad' or 'better use another language because Haskell is not up for the task'. I want an actual answer. I've asked this question in the past in this and some other forums and never got a straight answer. If you reply with 'use the IO monad' or something similar, can you please say if whatever you propose allows for in place update of values? It's important to know, for performance reasons. I wouldn't want to start simulations in a language that requires me to reconstruct every object at every simulation step. I am asking for this because the answer to 'why Haskell' has always been for me 'why not Haskell: because I write simulations and performance is of concern to me'.
- bedman12345 2y agoAn example of how to use the io monad for simulations https://benchmarksgame-team.pages.debian.net/benchmarksgame/q6600/program/nbody-ghc-2.html https://benchmarksgame-team.pages.debian.net/benchmarksgame/... It’s one of the nicer to read ones I’ve seen. Still is terrible imo.
- gspr 2y agoUse the ST monad? :)
- louthy 2y agoIn your imperative language, imagine this: World simulation(Stream<Event> events, World world) => events.IsComplete ? world : simulation(applyEventToWorld(events.Head, world), events.Tail); World applyEventToWorld(Event event, World world) => // .. create a new World using the immutable inputs That takes the first event that arrives, transforms the World, then recursively calls itself with the remaining events and the transformed World. This is the most pure way of doing what you ask. Recursion is the best way to 'mutate', without using mutable structures. However, there are real mutation constructs, like IORef [1] It will do actual in-place (atomic) mutation if you really want in-place updates. It requires the IO monad. [1] https://hackage.haskell.org/package/base-4.20.0.1/docs/Data-IORef.html https://hackage.haskell.org/package/base-4.20.0.1/docs/Data-...
- kreyenborgi 2y agoI've used Haskell for a decade or so, and tooling has improved immensely, with ghcup and cabal sandboxing and HLS now being quite stable. Maybe I've been lucky, but I haven't found much missing in the library ecosystem, or maybe I just have a lower threshold for using other languages when I see something is easier to do with a library from Python or whatever (typically nlp stuff). The one thing I still find annoying about Haskell is compile times. For the project itself, one can do fast compiles during development, but say you want to experiment with different GHC versions and base libraries, then you have to wait forever for your whole set of dependencies to compile (or buy some harddrives to store /nix on if you go that route). And installing some random Haskell program from source also becomes a drag due to dependency compile times (I'm always happy when I see a short dependency tree). Still, when deps are all compiled, it really is a fun language to program in.
- transpute 2y ago> ghcup and cabal sandboxing Would you recommend using cabal or stack to package Haskell components in a Yocto layer, for sandboxed, reproducible, cross-compiled builds that are independent of host toolchains?
- cubefox 2y agoUsing "Maybe" as a positive example of what Haskell can do isn't right. Say you have a function with input of type A and output of type B, written (A -> B). The problem with Maybe ("option" types) then is that if you have a function, in production use, of type (X -> Maybe Y), you can't "weaken your assumptions for the input and strengthen your promises for the output" (which would be an improvement) and rewrite it to the type (Maybe X -> Y). Because then you would have to modify all the code which already uses the function. Since "A" and "Maybe A" are incompatible types. Which is illogical. Several other null-safe languages solve this correctly by allowing disjunctions of types (often called unions, though countless other type related things are also called unions). They have a type operator "|" (or) and the function type (X -> Y|Null) can be improved by rewriting the function to (X|Null -> Y). Code outside the function doesn't have to be changed: Accepting X or Null implies accepting X, and returning Y implies returning Y or Null.
- dsign 2y agodata Maybe a = Nothing | Just a deriving (Eq, Ord) >> Because then you would have to modify all the code which already uses the function, as "A" and "Maybe A" are incompatible types. Which is illogical. I'm not so sure about your statement. If the type of the function changes, revising its usage at every use point is good for your sanity. I would go further and say that sometimes Maybe X is too weak, because it doesn't contain precise semantics for its Nothing and Just x alternatives. For example, sometimes you want a `Nothing` for a value that hasn't yet been found, but could potentially be filled up the evaluation chain, e.g. `NothingYet`, and a different Nothing for a value that is conclusively not there, e.g. `TerrifyingVoid`. If you fork your `Nothing` value into these two variants after you discover the need for it, you will have to revise each call anyway, and check what's the proper course of action. And this is a feature I wish I could use from Python. More generally, in large Haskell code bases, having the type-checker help you track, at compile time, code that breaks far away from where you made your changes, is an incredible time-saver.
- cubefox 2y agoYes, there are edge cases where you would like to have multiple different "kinds of null", but those use cases seem so uncommon in practice that they are mostly irrelevant.
- robertlagrant 2y agoI, like probably many people, like the idea of Haskell, but don't need a bottom-up language tutorial. Instead, I need: - how easy is it to make a web application with a hello world endpoint? - How easy is it to auth a JWT? - Is there a good ORM that supports migrations? - Do I have to remodel half my type system because a product owner told me about this weird business logic edge case we have to deal with? - How do I do logging? Etc.
- gtf21 2y ago> - how easy is it to make a web application with a hello world endpoint? If that's all you want it to do, it's very easy with Wai/Warp. > - How easy is it to auth a JWT? We don't use JWTs, but we did look at it and Servant (which is a library for building HTTP APIs) has built in functionality for them. > - Is there a good ORM that supports migrations? There are several with quite interesting properties. Some (like persistent) do automatic migrations based on your schema definitions. Others you have to write migration SQL/other DSL. > - Do I have to remodel half my type system because a product owner told me about this weird business logic edge case we have to deal with? I think that's going to really depend on how you have structured your domain model, it's not a language question as much as a design question. > - How do I do logging? We use a library called Katip for logging, but there are others which are simpler. You can also just print to stdout if you want to.
- robertlagrant 2y agoThank you! What I was more saying was that an article like this would do better showing some practical simple examples, that would let people do things, rather than bemoaning how Haskell is viewed in 2024.
- gtf21 2y agoOh! I hope I wasn't bemoaning too much -- that was the lead-in, but it's mostly about what I really like about the language (and had some examples but I also didn't want to write a tutorial).
- 2y ago
- 0xTJ 2y agoWhile the article is interesting, I find the layout of this website infuriating. A narrow strip of text, each line holding ~a dozen words makes it so much longer vertically than it needs to be, on desktop. I ended up using Inspect Element to change the width from 600px to 1200px, to fill up the most comfortable reading area on-screen.
- gtf21 2y agoSorry to hear that. I built it that way because I prefer reading narrower columns of text (maybe because I read a lot of magazines and newspapers, who knows).
- 0xTJ 2y agoFair enough, if that's what you prefer than you might as well, it's more of a personal preference. My main monitor is an ultra-wide, so it ends up using less than 20% of the total width. Though I can see it being tough to have a good solution that works everywhere.
- YuukiRey 2y agoThe general recommendation is the keep the measure of the page fairly narrow since studies show that reading text that is very wide is harder than reading a narrower column of text. So looking at the layout from a best practices point of view the author made the right call.
- maleldil 2y agoI feel like part of the problem is Haskell's extremism towards purity and immutability. I find some code easier to express with procedural/mutable loops than recursion, and I believe the vast majority of programmers. I think that one thing that makes Rust so successful is its capable type of system and use of many functional idioms, but you can use loops and mutability when it's more comfortable. And of course, the borrow checker to ensure that such mutability is sound.
- pyrale 2y agoThat's a problem no haskell user has, honestly. Your issue seems to be about getting your feet wet. Could you imagine people saying the issue with Java is its extremism towards objects and method calls? Sure, a determined Fortran programmer can write Fortran in any language, but if they have trouble doing so, maybe the issue isn't the language.
- bedman12345 2y ago> Could you imagine people saying the issue with Java is its extremism towards objects and method calls? I think exactly that all the time. It’s ridiculous. > That's a problem no haskell user has, honestly. I had this problem all the time when trying to write games in Haskell. Not every subject matter decomposes into semirings. Just like not everything decomposes nicely into objects. People tried to fix this with FRP or lenses. Both are worse than imperative programming for games imo.
- Joker_vD 2y ago> That's a problem no haskell user has, honestly. In a sense, that's true: people who do have this trouble constantly (e.g. me) very quickly cease being Haskell users. But that's hardly an argument for TFA's claim that "Haskell is probably the best choice for most programmers, especially if one cares about being able to productively write robust software, and even more so if one wants to have fun while doing it"; if anything, it's a counter-argument.
- deleted 2y ago[deleted]
- imoverclocked 2y agoMy biggest gripe with Haskell, especially when dealing with lower level code, is that there is no implicit enforcement of dealing with error states. I like golang far more in this regard. All of the “if error” guards may be ugly but they sure impose a culture of dealing with problems that will arise. I’ve come across plenty of Haskell code that just expects a happy path all of the time and can’t deal with any other situation. That’s great for POC work but horrible in production.
- HelloNurse 2y agoAnd also horrible for the typical functional programmer that likes clever "solutions" but hates the "boring" parts of actual good software.
- gtf21 2y agoI write about this at some length in the essay, perhaps you can help me by telling me why the section on "Make fewer mistakes" _doesn't_ satisfy?
- spopejoy 2y agoThere are real dragons handling AsyncException vs Exception, with extremely poor ecosystem understanding about how to deal with them properly. There's also the huge performance divergence between IO exceptions (fast) vs a mtl stack built around Either which will have huge problems successfully inlining and thus be slowwww. Indeed this is a great example of how Haskell can have serious performance issues in areas that would never occasion a second look in other mature GP langs. Who ever heard of well-modelled error handling having performance problems? Only In HaskellTM
- weebull 2y agoI think one of the big takeaways from Haskell for me was that errors don't always need to be explicitly handled. Sometimes returning a safe sentinel value is enough. For example, if the function call returns some data collection, returning an empty collection can be a safe way to allow the program to continue in the case of something unexpected. I don't need to ABORT. I can let the program unwind naturally as all the code that would work on that collection would realise there's nothing to do. Debugging that can be a pain, but traces and logging tend to fix that.
- Vosporos 2y agoAt work we use Haskell, and have been for around 10 years. It's a delight for iterations and refactoring, as the solid foundations it bring relieve you of spending your time writing tests checking for rogue nils or undefined.
- sevensor 2y agoNot a word about laziness? This is at this point the most interesting thing about Haskell. As many others have pointed out, the type system has hugely influenced other languages, but laziness by default, for everything, seems like an equally big deal, and equally hard to get one’s head around.
- gtf21 2y agoNope: I think the laziness aspect is very interesting, but it's not something that makes Haskell (for me) a great programming language. Or, at least, it's not in my list of the top reasons (it is in there somewhere).
- whateveracct 2y agoYou don't even notice the laziness most of the time. Even if you are benefiting from it.
- dbacar 2y agoWhy not?
- iso8859-1 2y ago> We can generalise this idea of being forced to handle the failure cases by saying that Haskell makes us write total functions rather than partial functions. Haskell doesn't prevent endless recursion. (try e.g. `main = main`) As the typed FP ecosystem is moving towards dependent typing (Agda, Idris, Lean), this becomes an issue, because you don't want the type checker to run indefinitely. The many ad-hoc extensions to Haskell (TypeFamilies, DataKinds) are tying it down. Even the foundations might be a bit too ad-hoc: I've seen the type class resolution algorithm compared to a bad implementation of Prolog. That's why, if you like the Haskell philosophy, why would you restrict yourself to Haskell? It's not bleeding edge any more. Haskell had the possibility of being a standardized language, but look at how few packages MicroHS compiles (Lennart admitted to this at ICFP '24[0]). So the standardization has failed. The ecosystem is built upon C. The Wasm backend can't use the Wasm GC because of how idiosyncratic GHC's RTS is.[1] So what does unique value proposition does GHC have left? Possibly the GHC runtime system, but it's not as sexy to pitch in a blog post like this. [0]: Lennart Augustsson, MicroHS: https://www.youtube.com/watch?v=uMurx1a6Zck&t=36m https://www.youtube.com/watch?v=uMurx1a6Zck&t=36m [1]: Cheng Shao, the Wasm backend for GHC: https://www.youtube.com/watch?v=uMurx1a6Zck&t=13290s https://www.youtube.com/watch?v=uMurx1a6Zck&t=13290s
- samvher 2y agoFor a long time already I've wanted to make the leap towards learning dependently typed programming, but I was never sure which language to invest in - they all seemed either very focused on just proofs (Coq, Lean) or just relatively far from Haskell in terms of maturity (Agda, Idris). I went through Software Foundations [0] (Coq) which was fun and interesting but I can't say I ever really applied what I used there in software (I did get more comfortable with induction proofs). You're mentioning Lean with Agda and Idris - is Lean usable as a general purpose language? I've been curious about Lean but I got the impression it sort of steps away from Haskell's legacy in terms of syntax and the like (unlike Agda and Idris) so was concerned it would be a large investment and wouldn't add much to what I've learned from Coq. I'd love any insights on what's a useful way to learn more in the area of dependent types for a working engineer today. [0] https://softwarefoundations.cis.upenn.edu/ https://softwarefoundations.cis.upenn.edu/
- watt 2y agoThe article lost me at following sentence: > A double arrow => describes constraints on the type variables used, and always come first: add1 :: Num a => a -> a describes a function which takes any type a which satisfies Num a, and returns a value of the same type. Here, I don't understand what `Num a` syntax means. It was not defined before. And, what does "satisfies" mean? It is also not defined before it is used. (It is also never mentioned again in the article.) It is maddening to read such sloppily constructed prose. Define your terms before you use them!
- ethangk 2y agoIt just means that ‘a’ must be a Number [0]. In this context, I believe satisfies means that it implements the things defined in the ‘minimum definition’ in the link below. If you’re familiar with Go, it’s similar to something implementing an interface. [0] https://hackage.haskell.org/package/base-4.20.0.1/docs/GHC-Num.html https://hackage.haskell.org/package/base-4.20.0.1/docs/GHC-N...
- watt 2y agowell why does Num then come before a ? If a :: Num would mean a is a value of type Num, why does this "satisfies" constraint does not follow the pattern?
- mrkeen 2y ago> If a :: Num would mean a is a value of type Num `a` is the type. Num is a `class`. Here's an example. x is an Int32 and y is an Int64. If they had type Num, then this would be valid: add :: Num -> Num -> Num -- Not valid Haskell add x y = x + y However it's not valid, because you can't add an Int32 and an Int64: add :: Int32 -> Int64 -> ? -- Doesn't compile add x y = x + y But you can add Nums together, as long as they're the same type. You indicate they're the same type by using the same type variable 'a': add :: a -> a -> a -- Doesn't compile add x y = x + y But now the above complains because you used (+) which belongs to Num, so you have to declare that these `a`s can (+) because they're Nums. add :: Num a => a -> a -> a add x y = x + y And it comes out shorter than your suggestion of putting the constraints afterward: add :: (a :: Num) -> (a :: Num) -> (a :: Num) -- Not valid Haskell add x y = x + y
- agentultra 2y agoIt's really good for boring, line of business software (BLOBS). The vast majority of business logic can be modelled with a handful of simple types and pattern matching. Very few design patterns are needed. And if you keep to the simple parts you can even teach the syntax (just the types) to non-technical contributors in an afternoon. Then they can read it and help verify that you implemented the business process correctly. It's also just nice for how my brain works. I like being able to substitute terms and get an equivalent program. Or that I can remember a handful of transformation rules that often get me from a first cut of a program to an efficient, fast one. And it's just fun.
- darby_nine 2y agoIf that's what you're looking for, why not rip out most of the language? You'll end up with something that looks a lot like Elm. You'll end up with a purely deterministic program with no i/o (albeit with a kind of crappy debugging experience).
- agentultra 2y agoWell because you need the rest of the language to make your program tell your system to do stuff. Turns out `IO` is the most essential and useful bit of a Haskell program. That part can be left to the programmers. Haskell has a lot of facilities for making that nicer to work with as well. I find that when I tell folks I work in Haskell full-time you can see their opinion of you change on their face. I'm not some kind of PhD genius programmer. I'm pretty middle-of-the-road to be honest. It's just nice to have a language that makes the work of writing BLOBS straight-forward.
- darby_nine 2y ago> Well because you need the rest of the language to make your program tell your system to do stuff. That's not necessary for business logic, though. This would presumably be embedded in infrastructure that handled i/o separately.
- 2y ago
- wredue 2y agoGood question Runtime immutability is brain dead Haskell doesn’t “solve maintainability” even remotely. Most people leaving Haskell say maintainability is worse Haskell doesn’t solve parallelism as Haskell devs claim Haskell is not beautiful And that’s literally every selling point. Why choose it?
- reidrac 2y agoI like haskell. Actually, let me rephrase that: I like GHC2021. And I have found that's one of the tricky bits with Haskell, together with the language being in very active development, wich makes upgrading your compiler a thing.
- tome 2y agoActually, changes to the compiler hardly ever break anything. I recently upgraded four compiler versions in a row and apart from a bug and some warnings the compiler didn't force any changes at all. It's primarily changes to libraries that introduce churn. https://h2.jaguarpaw.co.uk/posts/ghc-8.10-9.6-experience-report/ https://h2.jaguarpaw.co.uk/posts/ghc-8.10-9.6-experience-rep... (DeepSubsumption wouldn't have been a problem if we'd specified Haskell2010, as we should have.)
- igouy 2y agoAwaiting part 2 — Why not to use Haskell?
- sesm 2y agoHaskell is an experiment on having laziness at language level. This experiment clearly shows, that laziness on language level is a bad idea.You can get all the benefits of laziness at standard library level, as illustrated by Clojure and Twitter Storm using it in production. All the other FP stuff (union types, etc) existed before Haskell in non-lazy FP languages.
- agentultra 2y agoThere’s a strong case that laziness should be the default: https://m.youtube.com/watch?v=fSqE-HSh_NU https://m.youtube.com/watch?v=fSqE-HSh_NU I’m not sure I’m experienced enough in PLT enough to have a strong opinion myself. However, from experience, laziness has a lot of advantages both from a program construction and performance perspective.
- kccqzy 2y agoLaziness is but one mistake in Haskell. It should not prevent you from using other parts of the language that are wonderful. There's a reason Mu exists, which is to take Haskell and make it strict by default: there are plenty of good things about Haskell even if you consider laziness to be a mistake. (Of course a small minority of people don't consider laziness as a mistake as it enables equational reasoning; let's not go there.)
- tome 2y agoHaving used Mu I concluded that Haskell got function laziness correct. (Data type laziness is a different issue, but that can be solved by `StrictData`).
- kccqzy 2y agoThe problem with StrictData is that you need to convince every library in your dependency graph to switch to it, or provide strict versions of the data structures. Common container types like Map and Set do this. Your typical library implementing a domain-specific functionality does not.
- semiinfinitely 2y agoHaskell had a large impact on the design of JAX which is probably the future of ML development frameworks.
- drdrey 2y agoThe Python example feels like a strawman. You can write Python in a way that gets you most of the benefits of the Haskell version if you wanted to: @dataclass class InvalidResultError: result: str def do_something() -> int | InvalidResultError: result = get_result() return 42 if result == "a result" else InvalidResultError(result) Using a dataclass like this seems a little overkill, but it does get you something close to `Either`. I also don't understand the argument against nullable types: you could return `int | None` here, which would be the same as treating a `Maybe` (you have to explicitly consider the case where there is no value)
- deleted 2y ago[deleted]
- rbonvall 2y agoThe most important benefit is not that you CAN unwrap a value, but rather that you CANNOT NOT do it.
- jesse__ 2y agoThe thing that always amuses me when I read articles like this is that the things they point out as differentiating the language are usually, at best, small time-savers. 1. the lack of nullable types I very rarely write these bugs, and when I do I can typically fix them in 5 minutes. This is because I typically do (2) in my projects, which does largely eliminate this error. 2. representations of “failable” computations Basically any modern language can do this. It might not be 100% as ergonomic as it is in Haskell, but it also isn't a large source of bugs in my experience. 3. pattern matching and completeness checks Okay, these are nice and ergonomic in Haskell. Other languages get pretty close. Again, not a source of time-consuming bugs. 4. the avoidance of “primitive obsession” The example he gave for this is innanely contrived, and the bug would likely take a small amount of time to fix, even for a junior. Admittedly, this is a nice convenience feature and I would love having types for different color spaces, or radians vs degrees, but at the end of the day I spend basically 0% of my time on bugs like this, so it's barely worth mentioning. You know what I'd like a language to help me with? Keeping track of inter-data dependencies so I don't have to litter my code with a million assertions to make sure the sub-type of the sum type I'm working with is correct. Or giving me a way to express structural dependencies between pieces of code when writing multithreaded programs. Like saying "hey, this render pass has to happen after 'DoEntitySimulation' has completed" .. or whatever. I'm not aware of any language that even tries to do that, although I think Bungie wrote something like this in their engine for Destiny2. And metaprogramming. I ended up writing my own metaprogramming language because none of the ones I tried could even do the basics of what I wanted in an ergonomic way, and be runtime-performant. For reference, my language of choice is typically C++99. Maybe I'm not the intended target audience.
- valcron1000 2y agoThis is actually a very good comment. Haskell has had these features since early ~2000s and it was a major competitive advantage in the language space, but today I would argue that if you're using a modern language then they don't stand out as much. Nevertheless, not all popular languages provide all the above mentioned features and in case they implement them it's usually in a compromised fashion. For example, nullable types are still an open issue in Java, while C# and Typescript provide easy ways to circumvent them, a lot of times by accident (the main issue is that they're mostly annotations, not runtime checks). On the other hand, you mention several features which Haskell provides, usually through libraries that can only be implemented due to the features provided by the base language: - Refinement types [1] allow to add runtime invariants to existing types in an ergonomic fashion, or you can go even further and use something like LiquidHaskell[2] to enforce properties at compile time. - For multithreaded programs, the existence of STM[3] allows to to write mutable variables which are safe to use across threads. Very few languages offer something like this. - For structural dependencies, you can apply the techniques of "ghost of departed proofs"[4]. Personally I don't like to go that route since programming becomes an act of "proving" rather than "doing" but I appreciate the fact that you can encode it if you want/need to. - Metaprogramming in Haskell is not as ergonomic as in a LISP yet you have the full access to the language through TemplateHaskell[5]. A more constrained form is available as QuasiQuotations that allow you, for example to write a regex[6] string and have it compiled alongside the rest of the code. There are other features that I personally think are still far ahead from the competition, like lenses[7] to traverse nested data, the async[8] package for ergonomic concurrent programs, effect systems[9] for more granular dependency injection, immutability by default to avoid corrupting state, full type inference, top level functions and values (I can't believe the amount of times I find myself missing them in OOP languages like Java and C#), among others. --- [1] https://hackage.haskell.org/package/refined https://hackage.haskell.org/package/refined [2] https://ucsd-progsys.github.io/liquidhaskell/ https://ucsd-progsys.github.io/liquidhaskell/ [3] https://hackage.haskell.org/package/stm https://hackage.haskell.org/package/stm [4] https://hackage.haskell.org/package/gdp https://hackage.haskell.org/package/gdp [5] https://serokell.io/blog/introduction-to-template-haskell https://serokell.io/blog/introduction-to-template-haskell [6] https://hackage.haskell.org/package/regexqq https://hackage.haskell.org/package/regexqq [7] https://hackage.haskell.org/package/lens https://hackage.haskell.org/package/lens [8] https://hackage.haskell.org/package/async https://hackage.haskell.org/package/async [9] https://hackage.haskell.org/package/effectful https://hackage.haskell.org/package/effectful
- semiinfinitely 2y agoI love Haskell but I hate using it.
- jes5199 2y agoin general, I am in favor of language features that make it easy to prove that common errors will not happen. Conversely, I am against language features that make it easy to make new classes of errors that are hard to reason about. Haskell manages to do a lot of both. The kinds of problems I ran into in my Haskell error were much, much weirder than the problems I run into in other environments - things that when I explain them to other programmers they often don't even believe me. On balance, for me, the new problems were worse than the old problems, but your mileage may vary.
- alxmng 2y agoThis is my experience as well. Referential transparency and immutability have many advantages, with few disadvantages (if any). Type checking is great as a way to enforce constraints. However, nominal types create unnecessary incompatibility and endless type shuffling every time you want to make even simple changes. I maintain a web app written in Haskell and there’s 3 or 4 different types for URLs in the codebase, even though there’s no real difference between them. Nominal typing is terrible for code reuse via third-party modules. So many hours wasted wrapping types or shuffling between them. A functional language with a simple set of structural types would be the sweet spot for me. Clojure is probably the closest to this.
- birdfood 2y agoI think I'm at the same conclusion. I basically want ocaml but with structural / compile time duck typing of all types (I know about objects but they don't seem widely used). And some sort of reflection mechanism to cover 80% of the cases where you'd reach for ppx / macros (i.e. database interface code gen of types).
- joshlemer 2y agoWhat kinds of things led to multiple types of URL's in the same codebase?
- alxmng 2y agoWhen I first started the project, URLs needed certain constraints enforced in my business logic. So I thought "Great, let me create a type for URLs". This is the type that parses URLs from user input and gets marshaled/unmarshalled to the DB. Then I needed to ingest RSS feeds. So I found a library that handled that for me. Except that library uses another type for URLs. Uh oh. What should I do? I could change my URL type to be a wrapper type around both types, or write code to convert between the types. I chose to convert. Now I'm writing code to shuffle between the types where this RSS parsing module is used. Then I needed to make HTTP requests. So I pulled in a library to handle HTTP requests. Of course, that library uses another type for URLs (from another library it depends on). Great. Now I have 3 types for a URL. Then I needed to parse XML... and you know where this story is going. So now my codebase has many different URL types. The type-a-holics will say: "This is actually good! Each implementation of the URL type might have slightly different constraints, and the type system makes this all explicit. You should be grateful you spend half of your development time fiddling with types. The fact that `unpack . decodeUtf8` is littered around your codebase isn't code smell, it's the splendor of a type system that's saving you from yourself. You should learn to love the fact that you have to deal with String, Text, and ByteString and 4 URL types to fetch and parse an RSS feed. Otherwise your software would be full of bugs! Silly developer." One day I finally woke up from this type nonsense. There's integers, rationals, strings, lists, and maps. The end.
- me_vinayakakv 2y agoI was looking into the pattern matching example in the article with `Either` type. If we need to unwrap and check for all the cases one by one would it become a callback hell? I was going through a Scala codebase at work that uses `Future`s and `map`ing and `flatMap`ing them. Sometimes the callbacks went 5-6 levels deep. Is there a way to "linearlize" such code? I come from JS/TS background and have not much experience with pufe functional languages. But I love how TS handles discriminated unions - if we handle a branch and `return` early, that branch is removed from the union for the subsequent scope, and I was wondering if something of that sort can be achieved in Haskell/Scala.
- tel 2y agoIn Haskell, that's usually that's done using `do` syntax. do a <- somePartialResult b <- partialFunction1 a c <- partialFunction2 a b return c where we assume signatures like somePartialResult : Either<A, Error> partialFunction1 : A -> Either<B, Error> partialFunction2 : A -> B -> Either<C, Error> this overall computation has a signature Either<C, Error>. The way it works is that the first failing computation (the first Either that's actually Left-y) will short-circuit and become the final result value. Only if all of the partial computations succeed (are Right-y) will the final result by Right(c). In Haskell we don't have an early return syntax like `return` and function scope. Instead, we construct something equivalent using `do` syntax. This can be a little weightier than `return`, but the upside is that you can construct other variants of things like early returns that can be more flexible.
- me_vinayakakv 2y agoNice! Would it be possible to transform an error to something else using this syntax? Or, should we resort to a method of `Either` that transforms its `Left` in that case?
- tel 2y agoUnfortunately, no. Or, rather, I'm sure there's a way to make it happen although that's not typical practice. Typically you'd resort to mapping the left sides of your eithers so that the error types match. Rust offers a similar facility (though specialized to just handle a couple kinds of error handling) using its `?` syntax. This works essentially identically to the do syntax above, but also includes a call to transform whatever error type is provided into the error type of the function return. Note that in Rust (a) this technique only, today, works at function boundaries and (b) will always be explicitly annotated since all functions require an explicit type. This helps a bit over Haskell's more general approach as it provides some additional data to help type inference along. That said, if you were interested, it's likely possible to emulate something very similar to Rust's technique in Haskell, too. But I don't think I've ever seen that. It just doesn't feel as stylish in Haskell. The From/Into traits define a behavior that's much more pervasive than most type classes in Haskell. It works well for Rust, but is I think less compelling to the Haskell community.
- 1oooqooq 2y agoWhat's one big web facing application which handles modern authentication protocols?
- whateveracct 2y agomercury.com ?
- yakshaving_jgt 2y agoFor a bit more context, iirc mercury is at ~2m lines of Haskell over ~10k modules.
- nazka 2y agoIs it still true that writing optimized Haskell is extremely hard? Since Haskell is GC, can I write code as fast as say Java or Go?
- kevindamm 2y agoThis varies by use case and how much / which extensions you're including in your Haskell source (and, to a lesser extent, which libraries being included in the equivalent Java or Go source). I haven't taken a lot of measurements and my production code is biased to Go, C++, Python and Java, with most of my Haskell experience being side projects and toys for learning from, and writing a type-unifier for a production project. I can summarize what I learned but I would be interested in seeing better measurements. First, though, let's be more specific about what you mean by "writing code as fast," which I think should be refined to "time spent writing code" + "time compiling code" + "time spent in language runtime" + "time spent debugging / reading code". Depending on your project, and who if anyone is collaborating, each of these might be more or less important. Sometimes runtime speeds dwarf the needs of development or even debugging time. Sometimes compilation speeds afford the quick feedback loop that contributes to flow in development time. Within that framing, Haskell can excel at development time with small teams and limited scopes. It affords writing a domain-specific language within the code, including very custom operators, and this carries the risk of overburdening with complexity, and strongly proportional to the number of people on the team. Things can get complex fast and it can contribute some to compilation time if there is a lot of recursive complexity to the type system. But if the source is organized well and doesn't need to be very dynamic, this may not be much of a concern. As an underlying engine for very dynamic data inputs and sufficiently complex numerical analysis or IO management as its primary purpose, it would probably do well. The packaging system (Hackage) is pretty good, and that benefits the development time considerably. Adding some modules may impede compilation times, for much of the same reason as above, the type inference can become expensive. And undisciplined source management can lead to a lot of type implementations that are near copies of each other. Obviously this also ties into the reading/debugging time, too. For runtime, though, yes Haskell can be competitive with bare-metal C implementations. I think there have been a few papers written about that going back a decade or so.
- 2y ago
- skybrian 2y agoAs far as I've heard, Haskell's type system doesn't normally prove functions to be total; they can diverge. This fine, though, because for ordinary programming, a proof of totality isn't a useful guarantee. You care how long programs actually take to run, not whether they would theoretically finish eventually. It's only when proving theorems that a mathematical proof of totality matters, and there are specialized languages for that. For most people, we test in order to make a scientific claim, that we tried running it, and it worked for the inputs we tried, and completed in a reasonable amount of time. This is true of property testing and even model-checking; in simple cases, sometimes an exhaustive test can be done, but they don't actually prove statements outside the bounds used when testing.
- staunton 2y agoIt's perfectly feasible to have proofs about programs together with a guaranteed upper bound on (something like) the "number of processor instructions it will take" (given known finite bounds for all inputs). Of course, just like any other system that allows correctness proofs, it wouldn't be nearly useful enough to justify the effort for all but a negligible number of applications. That's at least until the levels of effort required are significantly reduced.
- skybrian 2y agoYes, a theoretical calculation like that would be useful as an estimate. But theoretical performance on ideal machines is only loosely related to performance on real machines under real conditions. That's true of testing, too. Benchmark performance varies even between runs. So there's still going to be a theoretical math versus science and engineering divide. Another perspective is that we have a useful division of concerns. Static checking is useful to find some kinds of errors. API's help to ensure that certain things don't change in a new version, so that performance improvements are less likely to break callers. Depending on the domain, leaving some things like performance and size limits deliberately unspecified in API's seems like more of a feature than a bug? Stricter interfaces aren't always an improvement.
- 2y ago
- adamddev1 2y agoI want Haskell with the tooling, DX, and packages of TypeScript.
- yakshaving_jgt 2y agoI don’t. I don’t think gradual typing is enough to cover for the unprincipled mess that I’ve observed in the JS world over the past 15 years.
- adamddev1 2y agoI meant the amount/functionality of the packages one finds in TypeScript, but written in good Haskell. A dream I know.
- hugodan 2y agoI'll tell you why, it is simply the best refactoring experience out there. This.
- devit 2y agoI think Haskell is fundamentally a bad design, because there is no reason to not have dependent types and totality checking in such a language, and also laziness is bad as it makes memory usage unpredictable and potentially asymptotically broken. Basically Rust is much better at producing efficient code with zero abstraction cost (while still doing a decent job at controlling mutation and having an expressive non-dependent type system) and having a large package ecosystem, and Lean, Agda and Idris are much better at being theoretically perfect languages while sacrificing code efficiency, so why use Haskell?
- mrkeen 2y agoI think (s/Haskell/Rust) is fundamentally a bad design, because there is no reason to not have dependent types and totality checking in such a language
- devit 2y agoSo far no one has managed to produce a programming language with dependent types that compiles to efficient machine code with zero overhead like Rust and C do, so the reason to not have dependent types in Rust is to be able to produce efficient code (which Haskell doesn't even without dependent types). Obviously if such a language is possible to make and gets made, it will be the strict best programming language overall and make all other languages obsolete (just like Rust obsoleted C/C++, etc.)
- instig007 2y ago[flagged]
- komali2 2y ago> All mainstream, general purpose programming languages are (basically) Turing-complete, and therefore any programme you can write in one you can, in fact, write in another. There is a computational equivalence between them. The main differences are instead in the expressiveness of the languages, the guardrails they give you, and their performance characteristics (although this is possibly more of a runtime/compiler implementation question). Yes, as I like to tell project managers, everything can be implemented. ... But the Google drive sdk is available in python, java, and node. Tooling, deployment, library ecosystem, and talent pool are just as important when choosing a language or framework as performance, syntax, or guardrails. Unless you have unlimited budget!
- the_precipitate 2y ago[dead]
- revskill 2y agoIt is all about monad.
- ackfoobar 2y agoI believe Haskell is worth learning. But I don't want to spend any more time near purely functional programmers in my professional life, so I will spend some time to unfairly nitpick the article. > a lot of the new features ... are either inspired by, or at least more robustly implemented in, Haskell. This is like some programmers, who don't know much about the history of programming languages, saying Java is stealing features from Kotlin (a language I enjoy). In both cases the inspiration usually comes from ML (a language family which includes OCaml - a language I deeply admire). Type classes (first implemented in Haskell) are neat though. I heard that Java is considering adopting them. > Let’s imagine we represented these as plain old strings ... `Theatre String String -- venue name and event name respectively` `checkForSeats :: String -> String -> IO [Seat]` If your language has named arguments, and sum type cases are records where you have to name your fields, using plain strings is just fine, and probably more ergonomic than wrapping the strings in another type. > `Either AddressParseError ValidAddress` Real world procedures usually involves many steps which can fail in many ways. The HM type system does not have subtypes. To stuff all possible failures into the `Left` case of `Either` you have to wrap all possible failure types into a sum type, possibly with multiple layers of nesting (huge PITA). > concurrent Haskell For all the hassle with the IO monad, you cannot offload your understanding of memory model to the compiler when you use an `IORef`. Maybe most your problems are embarrassingly parallel and STM is fast enough for the rest. Maybe. > exactly encode the effects we want a function to be permitted to perform https://degoes.net/articles/no-effect-tracking https://degoes.net/articles/no-effect-tracking "Effect Tracking Is Commercially Worthless" See "Tagless-Final Effect-Tracked Java™" for a chuckle. > small sample program The `Money` type is monomorphic, and `Functor`s are higher-kinded. You get the error message `Expected kind ‘* -> *’, but ‘Money’ has kind ‘*’`
- ParetoOptimal 2y ago> To stuff all possible failures into the `Left` case of `Either` you have to wrap all possible failure types into a sum type, possibly with multiple layers of nesting (huge PITA). Is this not inherent complexity? Is there some language or approach you believe makes this easier? Does it provide the same or similar correctness?
- psychoslave 2y ago>programming is not maths, and that anything that smells of maths should be excised Math is not academic gibberish though, and actually often the hard part to make a great conceptual thing work finely in the wild is to give it a more approachable form than the ridiculously arcan soup of greek letters and made up symbols sprinkled with generous quantity of opaque acronyms and words shrinked into trigrams. Not to say CS/IT industry always shines at making all these better, and it can actually be even more "enthusiast" with acronym jargon in my experience. No one communicate perfectly, sure starting with me. :D
- xigoi 2y agoDo you think mathematicians deliberately use Greek letters and “made up symbols” (whatever that even means) to make their work less accessible? Or could there possibly be a different reason?
- stoperaticless 2y agoTo seem cool and get laid.
- psychoslave 2y agoNo, at least I think that generally this is not like a voluntary conscious move from individual. Back in university, I remember how often my teacher would not be able to inform me about why this or that symbol was used, they were just reproducing what they had been introduced to without questioning its history and the relevance to keep it forward. Now to be fair, it seems that most people never care about that kind of "details" and are happy to just apply the formula as expected by whoever will assess your performance, get the degree and move forward in their career. Of course there are a few people out there who do deliberately select an obscure symbol just to put some esoteric vibe on the topic. But I believe that’s definitely not the norm. Lambda as selected by church is almost there, should I believe https://math.stackexchange.com/a/2095748/85628 https://math.stackexchange.com/a/2095748/85628 And in any case I by far prefer "anonymous function" as a term and prefer languages that doesn’t use `lambda` as keyword to implement them (looking at you Python!). Of course, just one symbol is not a big deal, but when each academic out there feels like they need to also introduce their special symbols to feel as special as the ones they admire, we end up with a mess of irrelevantly large number of symbols that don’t really bring a significant addition in term of expressiveness but do contribute to make things harder to grasp. https://en.wikipedia.org/wiki/Mathematical_operators_and_symbols_in_Unicode https://en.wikipedia.org/wiki/Mathematical_operators_and_sym... for what I have in mind about "made up symbols".
- IceDane 2y agoI started writing Haskell in something like.. 2008 or so. Before that, I was an avid low-level programmer that sneered at high level languages that held your hand. I quickly realized the folly of my ways, however, and became what I would call a Haskell zealot. It was my favorite and most-used programming language, and I even wrote it professionally very early in my career. As my career progressed and I had to use other languages, Haskell quickly lost its shine. It's just not a very practical language, no matter what they like to claim from their academic ivory tower. I wasn't some sort of novice. I could speak fluent lens and was well-versed in arcane type theoretic concepts, wrote entire data pipelines using recursion-schemes and what have you. The problem with Haskell it's just way too far out on the spectrum of purity, I think. Everything is way too cumbersome because the language is so rigid. Building real software in Haskell, while trying to make use of its advanced features, is sort of like wanting to do basic arithmetic for your budget, but starting out by proving that mathematics actually make sense. Naturally, you don't have to build your software like that, and it seems like a lot of actually practical haskell apps out there aren't written like this. Nevertheless, Haskell fundamentally changed the way I view programming and I learned many incredibly useful things, which I have taken with me. Try to keep your functions pure and small. Use static types, and lean on the type system to help you prove that you have covered all cases. Stuff like that.
- ur-whale 2y ago> Since the syntax is quite distant from C-like syntax THIS - and none of the other things cited in the article - is the main reason why Haskell is getting no traction in the real world. The syntax is an abomination, and worse, IMO pretty much unnecessary. The ideas of the language are interesting, and could be expressed in a much more familiar syntax which would be a huge win for the language (although the convoluted hoops you have to jump through when you truly and actually need to mutate data would still be a sheer nightmare). Haskell's syntax is exactly what keeps it niche and academic, except for the happy few who have managed to twist their brain into being able to parse that grotesque syntax.
- fsckboy 2y agotangential haskell question (I don't know haskell at all) does haskell have any facility for inspecting the "lazy evaluation queue" in a human readable way? I'm doing some binary-level math-as-logic calculations and I'd like to look at the patterns that emerge deep down, what cancels out and can be optimized away: think of it this way, I want to multiply AxB, where in bits thats (a0 a1 a2...) x (b0 b1 b2...) which turns into a bunch of a0 OR b0 , a0 XOR b0, (don't forget the carries :) etc, which after a few operations in an expression would be really hairy looking but completely straightforward. so, I can write a little program to do that in any language and print out what happens (lisp/scheme would be a good one). If a wrote a little program like that in Haskell, would it afford me any extra "free" options for inspecting what's going on? where if a0 and b3 where unknown, they'd be variables, but if I had actual values for them they could be evaluated away. maybe i should ask this as its own thread...
- rednafi 2y agoStill “impractical, academic, and niche”
- MrResearcher 2y agoI'd love to see a memory efficient and performant implementation of lock-free skip lists written in Haskell, and it'd better beat my C++ implementation! Laziness incurs amortized costs all over the place, and makes reasoning about the application state more difficult, can you help me justify the cost for the extra electricity? And, btw, last time I wrote a web server in Haskell, I was very much surprised that the standard way of implementing it was to use lenses, which mimic imperative style of assignments to a variable. And logging. Turned out you need logging for anything real, and in Haskell they are side effects implemented via a dirty hack that can be invoked anywhere. Finally, you can pry my Knuth tomes from my dead cold hands. On a more serious note, if you are really into immutability and functional style, F# is deemed to be a far better and practical choice. I reserve only praise for Common Lisp and Clojure, although I typically prefer static typing.