[HN Gopher] Why Haskell?
       ___________________________________________________________________
        
       Why Haskell?
        
       Author : mesaoptimizer
       Score  : 282 points
       Date   : 2024-09-12 08:06 UTC (14 hours ago)
        
 (HTM) web link (www.gtf.io)
 (TXT) w3m dump (www.gtf.io)
        
       | louthy wrote:
       | I 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 wrote:
         | If 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 wrote:
           | I 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
        
           | adastra22 wrote:
           | All bug reports are good. But is this not obvious? Do the
           | Haskell developers not use other language ecosystems? This
           | goes beyond "this edge case is difficult" and into "the whole
           | tooling stack is infamously hard to work with." I just
           | assumed Haskell, like eMacs, attracted a certain kind of
           | developer that embraced the warts.
        
             | mrkeen wrote:
             | No, we use plenty of other stuff.
             | 
             | My $DAYJOB language:
             | 
             | * Can't build a binary
             | 
             | * Uses an inexplicable amount of memory.
             | 
             | * Has an IDE which constantly puts itself into a bad state.
             | E.g. it highlights and underlines code with red even when I
             | know it's a pristine copy that passes its tests. I
             | periodically have to close the project, navigate to it in
             | the terminal, run 'git status --ignored' and delete all
             | that crap and re-open the project.
             | 
             | * Is slow to start up.
             | 
             | * Has a build system with no obvious way to use a 'master
             | list' of version numbers. In our microservice/microrepo
             | system, it is a PITA to try to track down and remove a
             | vulnerable dependency.
             | 
             | * Has been receiving loads of praise over the last 18
             | months for starting to include stuff that Haskell has
             | included for ages. How's the latest "we're solving the null
             | problem" going?
             | 
             | What the GHC compiler does for me is just so much better at
             | producing working software than $DAYJOB language +
             | professional $DAYJOB IDE, that I don't think about the
             | tooling.
             | 
             | If you want to put yourself in my shoes: imagine you're
             | getting shit done with TypeScript every day, and some C
             | programmers come along and complain that it's missing the
             | bare minimum of tools: strace, valgrind and gdb. How do you
             | even reply to that?
        
               | actualwitch wrote:
               | > If you want to put yourself in my shoes: imagine you're
               | getting shit done with TypeScript every day, and some C
               | programmers come along and complain that it's missing the
               | bare minimum of tools: strace, valgrind and gdb. How do
               | you even reply to that?
               | 
               | You tell them to strace/valgrind node whatever.js and
               | instead of gdb use built-in v8 debugger as node inspect
               | whatever.js
        
             | gtf21 wrote:
             | We do use other ecosystems, yes. I haven't really found the
             | tooling for Haskell to be particularly obstructive compared
             | to other languages. I've run into plenty of mysteries in
             | the typescript, python, ObjC/Swift, etc. ecosystems that
             | have been just as irritating (sometimes much more
             | irritating), and generally find that while HLS can be a bit
             | janky, GHC is very good and I spend less time scratching my
             | head looking at a piece of code that should work but does
             | something wild than in other languages.
        
         | mhitza wrote:
         | > * 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 wrote:
           | > 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 wrote:
             | Coincidentally 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.
        
               | louthy wrote:
               | > Since you mention F#, and C# in your previous comment,
               | are you on the Windows platform?
               | 
               | Linux Mint.
        
               | runevault wrote:
               | > Since you mention F#, and C# in your previous comment,
               | are you on the Windows platform?
               | 
               | Since dotnet core (now dotnet 5+), the Microsoft version
               | of dotnet has not been tied to windows outside a few
               | exceptions like old Windows UI libraries (WPF/WinForms)
               | and stuff like WCF once they revived it.
        
               | devmunchies wrote:
               | I've been using f# in production for 4+ years and haven't
               | used windows in like 15 years.
               | 
               | Speaking of LSP, the lsp standard is developed by
               | Microsoft so naturally any dotnet language will have good
               | lsp support.
        
             | nh2 wrote:
             | The Haskell LSP crashes less often now than 2 years ago. It
             | isn't perfect yet, but pretty usable for us.
        
             | rtpg wrote:
             | Has Scala gotten better because I remember it being quite
             | painful in the past (tho probably mostly due to language
             | issues more than anything)
        
               | draven wrote:
               | Well, there's Intellij IDEA with the scala plugin, and
               | it's pretty good. I regularly debug my code in the IDE
               | with conditional breakpoints, etc.
               | 
               | SBT still makes me want to throw the laptop through the
               | window.
        
               | pxc wrote:
               | In the pre-LSP era, I worked as a novice Scala developer,
               | and I didn most of my Scala work in Emacs with ENSIME. It
               | was pretty good. I imagine the language server is pretty
               | usable by now.
        
               | weebull wrote:
               | Scala's has made some horrible language compromises in
               | order to live on the JVM in my opinion.
        
               | bad_user wrote:
               | I'd argue that Scala's "compromises" in general make it a
               | better language than many others, independent of the JVM.
               | 
               | But we can talk specifics if you want. Name some
               | compromises.
        
               | bad_user wrote:
               | The IntelliJ IDEA plugin for Scala is built by Jetbrains,
               | so it has official support. It has its quirks, but so
               | does the Kotlin plugin.
               | 
               | Sbt is better than Gradle IMO, as it has a saner mental
               | model, although for apps you can use Gradle or Maven.
               | Sbat has had some awesome plugins that can help in bigger
               | teams, such as Scalafmt (automatic formatting), Scalafix
               | (automatic refactoring), Wartremover and others. Scalafmt
               | specifically is best in class. With Sbt you can also
               | specify versioning schemes for your dependencies and so
               | you can make the build fail on problematic dependency
               | evictions.
               | 
               | Scala CLI is also best in class, making it comfortable to
               | use Scala for scripting - it replaced Python and Ruby for
               | me: https://scala-cli.virtuslab.org/
               | 
               | Note that Java and Kotlin have Jbang, but Scala CLI is
               | significantly better, also functioning as a REPL. Worth
               | mentioning that other JVM languages hardly have a usable
               | REPL, if at all.
               | 
               | The Scala compiler can be slow, but that's when you use
               | libraries doing a lot of compile-time derivation or other
               | uses of macros. You get the same effect in similar
               | languages (with the exception of Ocaml). OTOH the Scala
               | compiler can do incremental compilation, and alongside
               | Sbt's support for multiple sub-projects or continuous
               | testing, it works fairly well.
               | 
               | Scala also has a really good LSP implementation, Metals,
               | built in cooperation with the compiler team, so you get
               | good support in VS Code or Vim. To get a sense of where
               | this matters, consider that Scala 3.5 introduces "best
               | effort compilation":
               | https://github.com/scala/scala3/pull/17582
               | 
               | I also like Kotlin and sadly, it's missing a good LSP
               | implementation, and I don't think Jetbrains is interested
               | in developing it.
               | 
               | Also you get all the tooling that's JVM specific,
               | including all the profilers and debuggers. With GraalVM's
               | native image, for example, Scala fares better than Java
               | actually, because Scala code relies less on runtime
               | reflection.
               | 
               | I'd also mention Scala Native or ScalaJS which are nice
               | to have. Wasm support is provided via LLVM, but there's
               | also initial support for Wasm GC.
               | 
               | So to answer your question, yes, Scala has really good
               | tooling compared to other languages, although there's
               | room for improvement. And if you're comparing it to any
               | other language that people use for FP, then Scala
               | definitely has better tooling.
        
               | ldite wrote:
               | SBT is awful. I've never used Gradle, but if SBT is saner
               | then I'm worried. This blogpost is a bit old, but still
               | on-target:
               | https://www.lihaoyi.com/post/SowhatswrongwithSBT.html
        
               | dionian wrote:
               | SBT has a learning curve but it also has a nice
               | ecosystem, for example sbt-native-packager is better than
               | its competitors in maven or gradle.
        
           | lkmain wrote:
           | I haven't yet felt the need for third party tooling in OCaml.
           | OCaml has real abstractions, easily readable modules and one
           | can keep the whole language in one's head.
           | 
           | Usually people do not use objects, and if they do, they don't
           | create a tightly coupled object mess that can only be
           | unraveled by an IDE.
        
           | mattpallissard wrote:
           | > Experimenting lately with OCaml, feels like Haskell's
           | tooling is more mature.
           | 
           | I feel like OCaml has been on a fast upward trajectory the
           | past couple of years. Both in terms of language features and
           | tooling. I expect the developer experience to surpass Haskell
           | if it hasn't already.
           | 
           | I really like Merlin/ocaml-lsp. Sure, it doesn't have every
           | LSP feature like a tool with a lot of eyes on it, such as
           | clangd, but it handles nearly everything.
           | 
           | And yeah, dune is a little odd, but I haven't had any issues
           | with it in a while. I even have some curve-ball projects that
           | involve a fair amount of C/FFI work.
           | 
           | My only complaint with opam is how switches feel a little
           | clunky. But still, I'll take it over pip, npm, all day.
           | 
           | I've been using OCaml for years now and compared to most
           | other languages, the experience has been pretty pleasant.
        
             | mhitza wrote:
             | My little experiments with OCaml have been pleasant thus
             | far (in terms of language ergonomics), but on the tooling
             | side Haskell (or rather I should say GHC) is pretty sweet.
             | 
             | For what I had to do thus far, at one point I needed to
             | step debug through my code. Whereas in GHC land I reload my
             | project in the interpreter (GHCi or cabal repl), set a
             | break point on function name and step through the
             | execution. With OCaml I have to go through the separate
             | bytecode compiler to build it with debug symbols and the I
             | can navigate through program execution. The nice thing is
             | that I can easily go back in execution in flow ("timetravel
             | debugging"), but a less ergonomic. Also less experienced
             | with this flow, to consider my issues authoritative.
             | 
             | I don't have that much experience with dune (aside from
             | setting up a project and running dune build), but one thing
             | that confused me at first, is that the libraries I have to
             | add to the configuration do not necessarily match the Opam
             | package names.
             | 
             | The LSP is fast, as mentioned before, it supports goto
             | definition, but once I jump to a definition to one of my
             | dependencies I get a bunch of squiggly lines in those files
             | (probably can't see transitive dependency symbols, if I
             | where to guess). I can navigate dependencies one level
             | deeper than I can with the Haskell language server, though.
             | 
             | I actually want to better understand how to build my
             | projects without Dune, and probably will attempt to do so
             | in the future. The same way I know how to manage a Haskell
             | project without Cabal. Feels like it gives me a better
             | understanding of the ecosystem.
        
           | innocentoldguy wrote:
           | Elixir's tooling is awesome, in my opinion.
        
         | moomin wrote:
         | No 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 wrote:
         | > 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 wrote:
           | Probably 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 wrote:
             | Spring is probably the worst framework created, so I
             | wouldn't list that as an example :/
        
           | louthy wrote:
           | The 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 wrote:
           | A 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
        
             | dwattttt wrote:
             | > Your best option was basically a `Maybe SomeResponseType`
             | which in the `None` case gave a 200 response with a
             | "{'status': 'error'}" content.
             | 
             | This seems to be an area where my tastes diverge from the
             | mainstream, but I'm not a fan of folding errors together.
             | I'd rather a http status code only correspond to the actual
             | http transport part, and if an API hosted there has an
             | error to tell me, that should be layered on top.
        
               | troupo wrote:
               | Well, that's why errors have categories:
               | 
               | HTTP status ranges in a nutshell:
               | 
               | 1xx: hold on
               | 
               | 2xx: here you go
               | 
               | 3xx: go away
               | 
               | 4xx: you fucked up
               | 
               | 5xx: I fucked up
               | 
               | (https://x.com/stevelosh/status/372740571749572610)
        
           | imoverclocked wrote:
           | I tried building a couple small projects to get familiar with
           | the language.
           | 
           | One project did a bunch of calculation based on geolocation
           | and geometry. I needed to output graphs and after looking
           | around, reached for gnuplot. Turns out, it's a wrapper around
           | a system call to launch gnuplot in a child process. There is
           | no handle returned so you can never know when the plot is
           | done. If you exit as soon as the call returns, you get to
           | race gnuplot to the temp file that gets automatically cleaned
           | up by your process. The only way to eliminate the race is by
           | sleeping... so if you add more plots, make sure you increase
           | your sleep time too. :-/
           | 
           | Another utility was a network oriented daemon. I needed to
           | capture packets and then run commands based on them... so I
           | reached for pcap. It uses old bindings (which is fine) and
           | doesn't expose the socket or any way to set options for the
           | socket. Long story short, it never worked. I looked at the
           | various other interfaces around pcap but there was always a
           | significant deficiency of some kind for my use case.
           | 
           | Now, I'm not a seasoned Haskell programmer by any means and
           | it's possible I am just missing out on something fundamental.
           | However, it really looks to me like someone did a quick hack
           | that worked for a very specific use-case for both of these
           | libraries.
           | 
           | The language is cool but I've definitely struggled with
           | libraries.
        
         | catgary wrote:
         | I 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 wrote:
           | Koka 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 wrote:
             | You'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.
        
               | psd1 wrote:
               | F# exists
        
               | cies wrote:
               | Yups. And that's about it for F#. One can await the
               | announcement that MSFT stops maintaining it.
        
               | RandomThoughts3 wrote:
               | People have been predicting the imminent demise of F#
               | since its first version 20 years ago.
        
               | devmunchies wrote:
               | A lot of major C# features were first implemented in F#.
               | I think of it as a place for Microsoft
               | engineers/researchers to be more experimental with novel
               | features that still need to target the CLR (the dotnet
               | VM). Sometimes even requiring changes to the CLR itself.
               | In that lens, it has had a very large indirect financial
               | impact on the dotnet ecosystem.
        
               | runevault wrote:
               | I keep (seems mistakenly) expecting them to try and push
               | F# along with their dotnet ML tooling more since, while
               | it is strictly typed, F# lets you mostly avoid making
               | your types explicit so exploration of ideas in code is
               | closer to Python than it is to c# while giving you the
               | benefits of a type system and lots of functional goodies.
        
               | mgdev wrote:
               | I'm just now discovering Koka. I'm kinda blown away.
               | 
               | I'm also a little sad at this defeatist attitude. What
               | you said might be true, but those things are solvable
               | problems. Just requires a coordinated force of will from
               | a few dedicated individuals.
        
               | bbkane wrote:
               | Be the change you want to see?
        
               | mgdev wrote:
               | Hear, hear!
        
               | catgary wrote:
               | There is a team of dedicated people working on Koka. They
               | say the language isn't production ready, and they don't
               | seem to be rushing. But I don't think they'd bother with
               | VSCode/IDE support if they didn't feel like they were
               | getting close.
        
         | pyrale wrote:
         | Most 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 wrote:
           | Counterpoint: 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 wrote:
           | If 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 wrote:
         | > compared to mainstream languages like C#
         | 
         | Out of curiosity does this also hold true for F#?
        
           | louthy wrote:
           | F#'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.
        
         | ants_everywhere wrote:
         | I 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 wrote:
           | > 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 wrote:
             | thanks!
        
         | robocat wrote:
         | It 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 wrote:
           | > 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 wrote:
           | > _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.
        
             | gtf21 wrote:
             | I think this is being taken as me saying "therefore you can
             | write any programme in Haskell" which, while true, was not
             | the point I was trying to make. Instead I was trying to
             | reduce the possible interpretation that I was suggesting
             | that Haskell can write more programmes than other
             | languages, which I don't think is true.
             | 
             | > computability and programming just aren't that related
             | 
             | I ... don't think I understand
        
         | RandomThoughts3 wrote:
         | The 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.
        
       | andrewstuart wrote:
       | Whenever 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-...
        
         | TacticalCoder wrote:
         | It'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 wrote:
         | And 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 wrote:
       | TL/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 wrote:
         | Ok, your Haskell example is basically drawing your opponent in
         | the wojack meme and declaring yourself the winner.
        
         | black_knight wrote:
         | The 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 wrote:
         | > 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_.
        
         | mmaniac wrote:
         | Your criticism seems more general than just Python and Haskell.
         | It's really about dynamic and static typing. That's a
         | legitimate debate, but as far as static typing goes, Haskell
         | has one of the best static systems around - and because of
         | that, idiomatic Haskell is unlikely to look like the example
         | you posted.
        
         | thesz wrote:
         | > the vast majority of time fixing problems in software
         | development is spent on systematic bugs and issues, not on
         | dealing with type errors.
         | 
         | You can make "systematic bugs and issues" a type errors, then
         | deal with them as type errors.
         | 
         | If you can confuse between meters and seconds expressed as
         | Double and erroneously add them, wrap them into types Meters
         | and Seconds, autogenerate Num and other classes'
         | implementations and voila, you cannot add Meters and Seconds
         | anymore, you get type error.
         | 
         | I do that even in C++, if I use it for my personal projects. ;)
         | 
         | But where Haskell really shine is in effect control. You cannot
         | open file and write into it during execution of transaction
         | between threads. If you parse text, you also can restrict
         | certain actions. There are many cases where you need control
         | over effects, and Haskell gladly helps there.
        
         | bmacho wrote:
         | > TL/DR: Haskell makes you add more meta information to the
         | code, so that compilers can reason about it.
         | 
         | Actually Haskell _lets_ you to add more meta information to the
         | code, similar to modern Python or TypeScript. Type information
         | is optional. But you might want to add it, it is helpful most
         | of the times.
         | 
         | In the example, doSomething implicitly depends on getResult,
         | which doesn't show up in the type information, so the type
         | information only tells you how you can use doSomething. To know
         | what _is_ doSomething, you actually have to read the code :\
        
           | gtf21 wrote:
           | I'm not sure that's entirely true (I wrote the examples): the
           | point I'm trying to make is that you can precisely describe
           | what `doSomething` consumes and produces (because it's pure)
           | and you don't have to worry about what some nested function
           | might throw or some side-effect it might perform.
        
             | bmacho wrote:
             | > I'm not sure that's entirely true (I wrote the examples)
             | 
             | Which part?
             | 
             | > the point I'm trying to make is that you can precisely
             | describe what `doSomething` consumes and produces (because
             | it's pure)
             | 
             | I think you failed to demonstrate it, and more or less
             | demonstrated the opposite of it: the type signature of
             | doSomething does not show its implicit dependence on
             | getResult.
             | 
             | In Haskell you can do                 foo :: Int       foo
             | = 5            bar :: Int       bar = foo + 1
             | 
             | (run it: https://play.haskell.org/saved/hpo3Yaef)
             | 
             | which your example does. In this example _bar_ 's type
             | signature doesn't tell you anything about what _bar_
             | 'consumes', and it doesn't tell you that _bar_ depends on
             | _foo_ , and on _foo_ 's type. Also you have to read the
             | body of bar, and also it is bad for code reuse.
        
               | gtf21 wrote:
               | > Which part?
               | 
               | This part: "the type information only tells you how you
               | can use doSomething. To know what is doSomething, you
               | actually have to read the code :\" I think we're
               | disagreeing on something quite fundamental here, based on
               | "it doesn't tell you that bar depends on foo, and on
               | foo's type. Also you have to read the body of bar, and
               | also it is bad for code reuse."
               | 
               | (Although I am certainly open to the idea that "[I]
               | failed to demonstrate it".)
               | 
               | A few things come up here:
               | 
               | 1. Firstly, this whole example was to show that in
               | languages which rely on this goto paradigm of error
               | handling (like raising exceptions in python) it's
               | impossible to know what result you will get from an
               | expression. The Haskell example is supposed to
               | demonstrate (and I think it _does_ demonstrate it) that
               | with the right types, you can precisely and totally
               | capture the result of an expression of computation.
               | 
               | 2. I don't think it's true to say that (if I've
               | understood you correctly) having functions call each
               | other is bad for code re-use. At some point you're always
               | going to call something else, and I don't think it makes
               | sense to totally capture this in the type signature. I
               | just don't see how this could work in any reasonable
               | sense without making every single function call have it's
               | own effect type, which you would list at the top level of
               | any computation.
               | 
               | 3. In Haskell, functions are pure, so actually you do
               | know exactly what doSomething consumes, and it doesn't
               | matter what getResult consumes or doesn't because that is
               | totally circumscribed by the result type of doSomething.
               | This might be a problem in impure languages, but I do not
               | think it is a problem in Haskell.
        
               | gtf21 wrote:
               | > In this example bar's type signature doesn't tell you
               | anything about what bar 'consumes'
               | 
               | Yes, it does: `bar` in your example is an `Int`, it has
               | no arguments. That is captured precisely in the type
               | signature, so I'm not sure what you're trying to say.
        
         | lkmain wrote:
         | I prefer the Haskell version. One can read it as a full
         | sentence instead of being interrupted by the boring imperative
         | Python version that breaks the train of thought on every line.
        
       | cpa wrote:
       | Haskell 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 wrote:
         | Haskell 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 wrote:
           | True. 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 wrote:
             | >I read somewhere that OCaml had a greater influence on it
             | 
             | Whoever wrote that is wrong.
        
               | randomdata wrote:
               | If we could wave a magic wand and remove Haskell's
               | influence on Rust, Rust would still exist in some kind of
               | partial form. If we waved the same wand and removed
               | OCaml's influence, Rust would no longer exist at all.
               | 
               | You are the one who is wrong, I'm afraid.
        
               | lkitching wrote:
               | Which OCaml features exist in Rust but not Haskell? The
               | trait system looks very similar to Haskell typeclasses,
               | but I'm not aware of any novel OCaml influence on the
               | language.
        
               | randomdata wrote:
               | _> Which OCaml features exist in Rust but not Haskell?_
               | 
               | Rust's most important feature! The bootstrapped
               | implementation.
        
               | lkitching wrote:
               | I'm not convinced the implementation language of the
               | compiler counts as a feature of the Rust language. If the
               | argument is that Rust wouldn't have been invented without
               | the original author wanting a 'systems OCaml' then fine.
               | But it's possible Rust would still look similar to how it
               | does now in a counterfactual world where the original
               | inspiration was Haskell rather than OCaml, but removing
               | the Haskell influence from Rust as it is now would result
               | in something quite different.
        
               | randomdata wrote:
               | Rust isn't just a language, though.
               | 
               | Additionally, unlike some languages that are formally
               | specified before turning to implementation, Rust has
               | subscribed to design-by-implementation. The
               | implementation _is_ the language.
        
               | lkitching wrote:
               | That just means the semantics of the language are defined
               | by whatever the default implementation does. It's a big
               | stretch to conclude that means Rust 'was' OCaml in some
               | sense when the compiler was written with it. Especially
               | now the Rust compiler is written in Rust itself.
        
               | randomdata wrote:
               | You're overthinking again. Read what is said, not what
               | you want it to say in some fairytale land.
        
               | TwentyPosts wrote:
               | The original rust compiler was written in OCaml. That's
               | not evidence it "had an influence", but it's highly
               | striking considering how many other languages Greydon
               | could've used.
        
               | hollerith wrote:
               | Yes: if a person knows _nothing else_ about Rust and the
               | languages that might have influenced it, then the fact
               | that the original Rust compiler was written in OCaml
               | should make that person conclude _tentatively_ that OCaml
               | was the language that influenced the design of Rust the
               | most.
               | 
               | I'm not one to hold that one shouldn't form tentative
               | conclusions until one "has all the fact". Also, I'm not
               | one to hold that readers should trust the opinion of an
               | internet comment writer they know nothing about. I could
               | write a long explanation to support my opinion, but I'm
               | probably not going to.
        
               | dimitrios1 wrote:
               | It's like trying to say Elixir wasn't influenced the most
               | by Erlang
        
               | hollerith wrote:
               | Was any Elixir interpreter or compiler every written in
               | Erlang?
               | 
               | If not, what is the relevance of your comment?
        
               | jolux wrote:
               | Elixir's implementation still has significant parts
               | written in Erlang. I don't know if it's a majority but
               | it's a lot. e.g.: https://github.com/elixir-
               | lang/elixir/blob/aef7e4eab521dfba9...
        
               | itishappy wrote:
               | Rust was bootsrapped in OCaml.
               | 
               | https://github.com/mozilla/rust/tree/ef75860a0a72f79f9721
               | 6f8...
        
           | setopt wrote:
           | I think it does, actually. Python also has many of Haskell's
           | features (list comprehensions, map/filter/reduce, itertools,
           | functools, etc.). But I only started reaching for those
           | features after learning about them in Haskell.
           | 
           | In Python, it's very easy to just write out a for-loops to do
           | these things, and you don't necessarily go looking for
           | alternative ways to do these things unless you know the
           | functional equivalents already. But in Haskell you're forced
           | to do things this way since there is no for-loop available.
           | But after learning that way of thinking, the result is then
           | more compact code with arguably less risk of bugs.
        
             | z500 wrote:
             | If anything, Python encourages you to use loops because the
             | backwards arrangement of the arguments to map and filter
             | makes it painful to chain them.
        
               | sgarland wrote:
               | map(function, iterable)
               | 
               | That seems very logical to me, but then, I'm not a
               | functional programmer, I just like map. It's elegant,
               | compact, and isn't hard to understand. Not that list
               | comps are hard to understand either, but they can
               | sometimes get overly verbose.
               | 
               | filter has also lost ground in favor of list comps,
               | partially because Guido hates FP [0], and probably due to
               | that, there has been a lot of effort towards optimizing
               | list comps over the years, and they're now generally
               | faster than filter (or map, sometimes).
               | 
               | [0]:
               | https://www.artima.com/weblogs/viewpost.jsp?thread=98196
        
               | BeetleB wrote:
               | Yes, but how do you chain them?
               | map(func4, map(func3, map(func2, map(func1, iter))))
               | 
               | vs                   iter.map(f1).map(f2).map(f3).map(f4)
               | 
               | I made up the syntax for the last one, but most
               | functional languages have a nice syntax for it. Here's
               | F#:                   iter |> f1 |> f2 |> f3 |> f4
               | 
               | Or plain shell:                   command | f1 | f2 | f3
               | | f4
        
               | TylerE wrote:
               | You don't.
               | 
               | Use generator syntax, which is really the more pythonic
               | way to it.                   >>> iter = [1,2,3,4]
               | >>> f1 = lambda x: x*2         >>> f2 = lambda x: x+4
               | >>> f3 = lambda x: x*1.25         >>> [f3(f2(f1(x))) for
               | x in iter]         [7.5, 10.0, 12.5, 15.0]
        
               | BeetleB wrote:
               | First off, writing f3(f2(f1(x))) is painful - keeping
               | track of parentheses. If you want to insert a function in
               | the middle of the chain you have some bookkeeping to do.
               | 
               | Second, that's all good and well if all you want to do is
               | map. But what if you need combinations of map and filter
               | as well? You're suddenly dealing with nested
               | comprehensions, which few people like.
               | 
               | In F#, it'll still be:                   iter |> f1 |> f2
               | |> f3 |> f4
               | 
               | Here's an example from real code I wrote:
               | graph         |> Map.filter isSubSetFunc         |>
               | Map.filter doesNotContainFunc         |> Map.values
               | |> Set.ofSeq
               | 
               | This would not be fun to write in List Comprehensions,
               | but you could manage (only two list comprehensions). Now
               | here's other code:                   graph         |>
               | removeTerminalExerciseNodes         |> Map.filter isEmpty
               | |> Map.keys         |> Seq.map LookUpFunc         |>
               | Seq.map RemoveTrivialNodes         |> Seq.sortBy
               | GetLength         |> Seq.rev         |> Seq.toList
               | 
               | BTW, some of the named functions above are defined with
               | their own chain of maps and filters.
        
               | Izkata wrote:
               | An alternative for python is to flip what you're
               | iterating over at the outermost level. It's certainly not
               | as clean as F# but neither is it as bad as the original
               | example if there's a lot of functions:
               | iter = [1,2,3,4]       f1 = lambda x: x*2       f2 =
               | lambda x: x+4       f3 = lambda x: x*1.25
               | result = iter       for f in [f1, f2, f3]:         result
               | = [f(v) for v in result]
               | 
               | Then the list comprehension can be moved up to mimic more
               | closely what you're doing with F#, allowing for
               | operations other than "map":                  result =
               | iter        for f in [          lambda a: [f1(v) for v in
               | a],          lambda a: [f2(v) for v in a],
               | lambda a: [f3(v) for v in a],        ]:          result =
               | f(result)
               | 
               | And a step further if you don't like the "result"
               | reassignment:                 from functools import
               | reduce       result = reduce(lambda step, f: f(step), [
               | lambda a: [f1(v) for v in a],         lambda a: [f2(v)
               | for v in a],         lambda a: [f3(v) for v in a],
               | ], iter)
        
               | BeetleB wrote:
               | Fair, but how would it look if you had some filters and
               | reduces thrown in the middle?
               | 
               | In my F# file of 300 lines[1], I do this chaining over 20
               | times in various functions. Would you really want to
               | write the Python code your way every time, or wouldn't
               | you prefer a simpler syntax? People generally don't do it
               | your way often because it has a higher mental burden than
               | it does with the simple syntax in F# and other languages.
               | I don't do it 20 times because of an obsession, but
               | because it's natural.
               | 
               | [1] Line count is seriously inflated due to my habit of
               | chaining across multiple lines as in my example above.
        
               | Izkata wrote:
               | My example was just a way to do it with plain python and
               | nothing special. There are libraries that use operator
               | overloading to get more F#-style syntax.
               | 
               | For example: https://ryi.medium.com/flexible-piping-in-
               | python-with-pipey-...
               | 
               | And another mentioned there:
               | https://pypi.org/project/pipe/
        
               | Izkata wrote:
               | You're not using generator syntax anywhere in that
               | example.
        
               | itishappy wrote:
               | Generators require a __next__ method, yield statement, or
               | generator comprehension. What you've got is lambdas and a
               | list comprehension. Rewriting using generators would look
               | something like:                   items = [1,2,3,4]
               | gen1 = (x*2 for x in items)         gen2 = (x+4 for x in
               | gen1)         gen3 = (x*1.25 for x in gen2)
               | result = list(gen3)
               | 
               | It's nicer in a way, certainly closer to the pipe syntax
               | the commenter your replying to is looking for, but kind
               | of janky to have to name all the intermediate steps.
        
               | itishappy wrote:
               | Don't Haskell and Python use the same argument order?
               | filter(lambda x: x<5, map(lambda x: 2*x, [1,2,3,4,5]))
               | filter (<5) . map (*2) $ [1,2,3,4,5]
               | 
               | (Technically the Python version should be cast to a list
               | to have identical behavior.)
               | 
               | Same with comprehensions (although nesting comprehensions
               | will always get weird).                   [x for x in
               | [2*x for x in [1,2,3,4,5]] if x<5]              [x | x <-
               | [2*x | x <- [1,2,3,4,5]], x<5]
        
           | odyssey7 wrote:
           | Check out Swift, too!
        
             | adastra22 wrote:
             | Why would I use swift when more cross-platform solutions
             | exist?
        
           | TylerE wrote:
           | Heck, even coming from Python (2) it felt very underwhelming
           | and hugely oversold. (Edit: To be fair, I'd done a bit of
           | Ocaml years earlier so algebraic data types weren't some huge
           | revelation).
           | 
           | Laziness is mostly an anti-pattern.
        
         | gtf21 wrote:
         | > Granted, the tooling is sh*t.
         | 
         | I hear this a lot, but am curious about two things: (a) which
         | bit(s) of the toolchain are you thinking about specifically --
         | I know HLS can be quite janky but I haven't really been blocked
         | by any tooling problems myself; (b) have you done much Haskell
         | in production recently -- i.e. is this scar tissue from some
         | ago or have you tried the toolchain recently and still found it
         | to be lacking?
        
           | n_plus_1_acc wrote:
           | Everytime I use cabal and/or stack, it gives me a wall of
           | errors and i just reinstall everyrhing all the time.
        
             | tome wrote:
             | If you share a transcript from a cabal session I'll look
             | into this for you.
        
             | simonmic wrote:
             | And if you share stack transcripts I'll look into those for
             | you.
             | 
             | I've experienced this too, the tools can certainly be
             | improved, but also a little more understanding of what they
             | do and how to interpret their error messages could help you
             | (I am guessing).
        
         | moomin wrote:
         | Sum types are finally coming to C#. That'll make it the first
         | "Mainstream" language to adopt them. Will it be as solid and
         | simple as Haskell's implementation? Of course not. Will having
         | a backing ecosystem make up for that deficiency? Yes.
        
           | n_plus_1_acc wrote:
           | Rust is mainstream, just not use in enterprise applications
        
           | SkiFire13 wrote:
           | What counts as mainstream for you?
           | 
           | Java has recently added sealed classes/interfaces which offer
           | the same features as sum types, and I would argue that Java
           | is definitely mainstream.
           | 
           | Kotlin has a similar feature. It might be used less than
           | Java, but it's the default language for Android.
           | 
           | Swift has `enum` for sum types and is the default language
           | for iOS and MacOS.
           | 
           | Likewise for Rust, which is gaining traction recently.
           | 
           | Typescript also has union/sum types and is gaining lot of
           | traction.
        
             | zozbot234 wrote:
             | For that matter, PASCAL has had variant records (i.e. sum
             | types) since the 1970s.
        
               | iso8859-1 wrote:
               | Did it have an ergonomic way to exhaustively match on all
               | the variants? Since the 70s?
               | 
               | How does the ABI work? If a library adds a new
               | constructor, but I am still linking against the old
               | version, I imagine that it could be reading the wrong
               | fields, since the constructor it's reading is now at a
               | different index?
        
             | thfuran wrote:
             | Sealed classes still won't let you have e.g.
             | String|Integer, though I'll grant you that java is
             | certainly mainstream.
        
               | kagakuninja wrote:
               | Scala 3 has had union types for 4 years now. Scala can be
               | used to do Haskell style pure FP, but with much better
               | tooling. And it has the power of the JVM, you can fall
               | back to Java libraries if you want.
        
               | SkiFire13 wrote:
               | You don't really need `String|Integer`, for most usecases
               | an isomorphic type that you can exhaustively pattern
               | match on is more than enough, and sealed classes (along
               | with the support in `switch` expressions) does exactly
               | that.
        
           | pjmlp wrote:
           | Not really, other mainstream languages got there first.
        
           | pid-1 wrote:
           | Python has sum types
           | 
           | optional_int: int | None = None
        
             | nicoburns wrote:
             | Every dynamically typed language effectively has one big
             | Sum type that holds all of the other types. IMO this is one
             | reason why dynamic languages have been so popular (because
             | Sum types are incredibly useful, and mainstream statically
             | typed languages have historically had very poor support for
             | them).
        
             | TwentyPosts wrote:
             | This is semantically not the same as a sum type (as
             | understood in the sense of Rust, which is afaik the
             | academically accepted way)!
             | 
             | Python's `A | B` is a union operation, but in Rust a sum
             | type is always a disjoint union. In Python, if `A = B =
             | None`, then `A | B` has one possible instance.
             | 
             | In Rust, this sum type has two possible instances. This
             | might not sound like a big deal, but the semantics are
             | quite different.
        
               | pid-1 wrote:
               | Sorry, I could not grok the difference, even after
               | reading a few Rust examples.
               | 
               | def foo(int | None = None) ...
               | 
               | ... just means the variable's default value is None in a
               | function definition. But it could be either in an actual
               | function call.
        
         | setopt wrote:
         | > Haskell has had a profound impact on the way I think about
         | programming and how I architect my code and build services.
         | 
         | > And I've had a lot of fun micro-optimizing Haskell code for
         | Project Euler problems when I was studying.
         | 
         | Sounds a lot like my experience. I never really used Haskell
         | for "real work", where I need support for high-performance
         | numerical calculations that is simply better in other languages
         | (Python, Julia, C/C++, Fortran).
         | 
         | But learning functional programming through Haskell - mostly by
         | following the "Learn you a Haskell" book and then spending time
         | working through Project Euler exercises using it - had a quite
         | formative effect on how I write code.
         | 
         | I even ended up baking some functional programming concepts
         | into my Fortran code later. For instance, I implemented the
         | ability to "map" functions on my data structures, and made
         | heavy use of "pure functions" which are supported by the modern
         | Fortran standard (the compiler then checks for side effects).
         | 
         | It's however hard to go all the way on functional programming
         | in HPC contexts, although I wish there were better libraries
         | available to enable this.
        
           | nextos wrote:
           | > But learning functional programming through Haskell [...]
           | had a quite formative effect on how I write code.
           | 
           | I think it is a shame Haskell has gained a reputation of
           | being hard, because it can be an enriching learning
           | experience. Lots of its complexity is accidental, and comes
           | from the myriad of language extensions that have been created
           | for research purposes.
           | 
           | There was an initiative to define a simpler subset of the
           | language, which IMHO would have been great, but it didn't
           | take off: https://www.simplehaskell.org. Ultimately, one can
           | stick to Haskell 98 or Haskell 2010 plus some newer cherry-
           | picked extensions.
        
             | gtirloni wrote:
             | Sounds a lot like the C++ experience.
             | 
             | In my time learning Haskell a decade ago, it was rare to
             | find some code that wasn't using an experimental extension.
        
             | bbkane wrote:
             | I think Elm is a fantastic "simplified Haskell" with pretty
             | good beginner-friendly guides. It's unfortunate that Elm is
             | mostly tied to the frontend and has been effectively
             | abandoned for the last couple of years.
             | 
             | Interestingly, Elm has inspired a host of "successors",
             | including Gleam + Lustre, which look really great (I
             | haven't had a chance to really try them yet).
        
               | nextos wrote:
               | What about Roc or Koka? Or simply moving to OCaml? It's
               | looking pretty great after v5, with multicore and effect
               | handlers.
        
               | giraffe_lady wrote:
               | OCaml is great but the type system is actually quite
               | different from Haskell's once you get into it. It also
               | has many "escape hatches" out of the functional pathway.
               | Even if you approach it with a learner's discipline
               | you'll run into them even in the standard lib.
               | 
               | With haskell you can look to the ecosystem to see how to
               | accomplish specific things with a pure functional
               | approach. When you look at ocaml projects in that way you
               | often find people choosing not to.
        
               | mattpallissard wrote:
               | Oh but the OCaml module system is the bees knees.
        
               | giraffe_lady wrote:
               | Yeah I didn't mean any of this as a negative lol. I
               | haven't touched haskell since I learned ocaml. I still
               | think haskell has the edge as an educational language for
               | functional programming and type systems though, which is
               | kind of what we're talking about but not entirely.
        
               | mattpallissard wrote:
               | No worries, I was just adding my two cents.
        
               | earth_walker wrote:
               | Elm's strengths are its constraints, which allow for
               | simple, readable code that's easy to test and reason
               | about - partly because libraries are also guaranteed to
               | work within those constraints.
               | 
               | I've tried and failed several times to write Haskell in
               | an Elm style, even though the syntax is so similar. It's
               | probably me (it's definitely me!), but I've found that as
               | soon as you depend on a library or two outside of prelude
               | their complexities bleed into your project and eventually
               | force you into peppering that readable, simple code with
               | lifts, lenses, transformations and hidden magic.
               | 
               | Not to mention the error messages and compile times make
               | developing in Haskell a chore in comparison.
               | 
               | p.s. Elm has not been abandoned, it's very active and
               | getting better every day. You just can't measure by
               | updates to the (stable, but with a few old bugs) core.
               | For a small, unpopular language there is so much work
               | going into high quality libraries and development tools.
               | Check out
               | 
               | https://elmcraft.org/lore/elm-core-development
               | 
               | for a discussion.
               | 
               | Elm is so nice to work in. Great error messages, and near
               | instant compile times, and a great ecosystem of static
               | analysis, scaffolding, scripting, and hot reloading tools
               | make the live development cycle super nice - it actually
               | feels like what the lispers always promised would happen
               | if we embraced repl-driven development.
        
               | bbkane wrote:
               | Thanks for the Elmcraft FAQ link. It's a great succinct
               | explanation from the Elm leadership perspective (though
               | tellingly not from the Elm leadership).
               | 
               | I feel like I understand that perspective, but I also
               | don't think I'm wrong in claiming Elm has been
               | effectively abandoned in a world where an FAQ like that
               | needs to be written.
               | 
               | I'm not going to try to convince you though, enjoy Elm!!
        
               | britzkopf wrote:
               | I've often wondered if it having a reputation as being
               | hard is accurate. Not necessarily because of syntax etc.
               | but because of you don't already have a grounding in
               | programming/engineering/comp sci. it can be difficult to
               | fit the insights Haskell provides into any meaningful
               | framework. That was my experience anyway, came to it too
               | early and didn't understand the significance.
        
           | l5870uoo9y wrote:
           | Pure functions are a crazy useful abstractions. Complex
           | business logic? Extract it into a type-safe pure function.
           | Still to "unsafe"? Testing pure functions are fast and
           | simple. Unclear what a complex function does? Extract it into
           | meaningful pure functions.
        
         | jillesvangurp wrote:
         | I think the tooling being not ideal is a reflection of how
         | mature/serious the community is about non academic usage.
         | Haskell has been around for ages but it never really escaped
         | its academic nature. I actually studied in Utrecht in the
         | nineties where there was a lot of activity around this topic at
         | the time. Eric Meyer who later created F# at MS was a teacher
         | there and there was a lot of activity around doing stuff with
         | Gopher which is a Haskell predecessor, which I learned and used
         | at the time. All our compiler courses were basically fiddling
         | with compiler generator frameworks that came straight out of
         | the graduate program. Awesome research group at the time.
         | 
         | My take on this is that this was all nice and interesting but a
         | lot of this stuff was a bit academic. F# is probably the
         | closest the community got to having a mature tooling and
         | developer ecosystem.
         | 
         | I don't use Haskell myself and have no strong opinions on the
         | topic. But usually a good community response to challenges like
         | this is somebody stepping up and doing something about it. That
         | starts with caring enough. If nobody cares, nothing happens.
         | 
         | Smalltalk kicked off a small tool revolution in the nineties
         | with its refactoring browser. Smalltalk was famous for having
         | its own IDE. That was no accident. Alan Kay, who was at Xerox
         | PARC famously said that the best way to predict the future was
         | to invent it. And of course he was (and is) very active in the
         | Smalltalk community and its early development. Smalltalk was a
         | language community that was from day one focused on having
         | great tools. Lots of good stuff came out of that community at
         | IBM (Visual Age, Eclipse) and later Jetbrains and other IDE
         | makers.
         | 
         | Rust is a good recent example of a community that's very
         | passionate about having good tools as well. Down to the
         | firmware and operating system and everything up. In terms of
         | IDE support they could do better perhaps. But I think there are
         | ongoing efforts on making the compiler more suitable for IDE
         | features (which overlap with compiler features). And of course
         | Cargo has a good reputation. That's a community that cares.
         | 
         | I use Kotlin myself. Made by Jetbrains and heavily used in
         | their IDEs and toolchains. It shows. This is a language made by
         | tool specialists. Which is why I love it. Not necessarily for
         | functional purists. Even though som Scala users have
         | reluctantly switched to it. And the rest is flirting with
         | things like Haskel and Elixir.
        
           | odyssey7 wrote:
           | "Greece, Rome's captive, took Rome captive."
           | 
           | The languages of engineering-aligned communities may appear
           | to have won the race, though they have been adopting
           | significant ideas from Haskell and related languages in their
           | victories.
        
             | mrkeen wrote:
             | Something went wrong in the adoption process.
             | 
             | Haskell's biggest benefit is _functions_ , not _methods_.
             | To define a function, you need to stop directly mutating,
             | and instead rely maps, folds, filters, etc. The bargain
             | was: you give up your familiar and beloved for-loops, and
             | in return you get software that _will yield the same output
             | given the same input_.
             | 
             | So what happened with the adoption? The Java people
             | willingly gave up the for-loops in favour of the
             | Streams/maps/filters. But they didn't take up the reward of
             | _software that yields the same input given the same
             | output_.
             | 
             | What's something else in the top-5 killer Haskell features?
             | No nulls. The value proposition is: _if you have a value,
             | then you have a value_ , no wondering about whether it's
             | "uninitialised". The penalty you pay for this is more
             | verbosity when representing missing values (i.e. Maybe).
             | 
             | Again, the penalty (verbose missing values ie. Optional<>)
             | was adopted, and the reward (confidently-present values)
             | was not.
        
               | odyssey7 wrote:
               | The type system is a big part and elements of that have
               | shown up elsewhere. I'm with you on the belief that we
               | should have better adoption for immutability, pure
               | functions, and equational reasoning.
               | 
               | JavaScript promises can work analogously to the Maybe
               | monad if you want them to.
               | 
               | Swift's optionals are essentially the same thing as the
               | Maybe monad.
        
               | the_af wrote:
               | > _Again, the penalty (verbose missing values ie.
               | Optional <>) was adopted, and the reward (confidently-
               | present values) was not._
               | 
               | Ah, the joys of having a Scala `Option` type and still
               | having to consider the cases or Some[thing], Nothing
               | and... null!
               | 
               | Yes, well-written Scala code knows not to use null with
               | reckless abandon, but since when your coworkers coming
               | from Java know to show restraint?
        
           | pyrale wrote:
           | > I think the tooling being not ideal is a reflection of how
           | mature/serious the community is about non academic usage.
           | 
           | I'd say it's more of a reflection of how having a very big
           | company funding the language is making a difference.
           | 
           | People like to link Haskell's situation to its academic
           | origins, but in reality, most of the issues with the
           | ecosystem are related to acute underfunding compared to
           | mainstream languages.
        
             | jillesvangurp wrote:
             | One doesn't happen without the other. Haskell is hugely
             | influential with it's ideas and impact. But commercially it
             | never really took off. Stuff like that needs to come from
             | within the community; it's never going to come from the
             | outside.
        
               | pyrale wrote:
               | > Stuff like that needs to come from within the community
               | 
               | Either the community is large enough for it, or it comes
               | from the sponsoring company.
               | 
               | Few languages start off by being in the first situation.
               | The first example that comes to my mind (Python), well...
               | Tooling was a long and painful road. And if the language
               | hadn't been used/backed by many prominent companies, I
               | don't see how man-hours would have flowed into tooling.
        
           | Nelkins wrote:
           | Pretty sure F# was created by Don Syme, not Erik Meijer.
        
         | cies wrote:
         | > Haskell has had a profound impact on the way I think about
         | programming and how I architect my code and build services.
         | 
         | Exactly the same for me.
         | 
         | > Granted, the tooling is sh*t.
         | 
         | Stack and Stackage (one of the package managers and library
         | distribution systems in Haskell-land) is the best I found in
         | any language.
         | 
         | Other than that I also found some tools to be lacking.
        
           | dario_od wrote:
           | What makes you say that stack is the best you found in any
           | language? I use it daily, and in my experience I'd put it
           | just a bit above PHP's composer
        
         | 0x3444ac53 wrote:
         | Would you mind explaining what you mean by stateless?
        
           | jgwil2 wrote:
           | Haskell functions are pure, like mathematical functions: the
           | same input to a function produces the same output every time,
           | regardless of the state of the application. That means the
           | function cannot read or write any data that is not passed
           | directly to it as an argument. So the program is "stateless"
           | in that the behavior does not depend on anything other than
           | its inputs.
           | 
           | This is valuable because you as the developer have a lot less
           | stuff to think about when you're trying to reason about your
           | program's behavior.
        
         | BoiledCabbage wrote:
         | > Give it a try. Especially, if you don't know what to expect,
         | I can guarantee that you'll be surprised!
         | 
         | And I will as _strongly_ as possible emphasize the opposite you
         | should not.
         | 
         | If you are are already experienced in functional programming,
         | as well as in statically typed functional programming or
         | something lovely in the ML family of languages then only then
         | does Haskell make sense to learn.
         | 
         | If you are looking to learn about either FP in general, or
         | staticly typed FP Haskell is about the single worst language
         | anyone can start with. More people have been discouraged from
         | using FP because they started with Haskell than is probably
         | appreciated. The effort to insight ratio for Haskell is
         | incredibly high.
         | 
         | You can learn the majority of the concepts faster in another
         | language with likely 1/10th the effort. For general FP learn
         | clojure, Racket, or another scheme. For statically typed FP
         | learn F# or Scala or OCAML or even Elm.
         | 
         | In fact if you really want to learn Haskell is is faster to
         | learn Elm and then Haskell than it is to just learn Haskel.
         | Because the amout or weeds you have to navigate through to get
         | to the concepts in Haskell are so high that you can first learn
         | the concepts and approach in a tiny language like Elm and it
         | will more than save the amount of time it would take to
         | understand those approaches from trying to learn Haskell. It
         | seems unbelievable but ai found it to be very try. You can
         | learn two languages faster than just one because of how muddy
         | Haskell is.
         | 
         | Now that said FP is valuable and in my opinion a cleaner design
         | and why in general our industry keeps shifting that way.
         | Monoids, Functors, Applicative are nice design patterns.
         | Pushing side effects to the edge of your code (which is
         | enforced by types) is a great practice. Monads are way
         | overhyped, thinking in types is way undervalued. But you can
         | get all of these concepts without learning Haskell.
         | 
         | So that's the end of my rant as I've grown tired of watching
         | people dismiss FP because they confuse the great concepts of FP
         | with the horrible warts that come with Haskell.
         | 
         | Haskell is a great language, and I'm glad I learned it (and am
         | in no way an expert at it)- but it is the single worst language
         | for an introduction to FP concepts. If you're already deep in
         | FP it's and awesome addition to your toolbox of concepts and
         | for that specific purpose I highly recommend it.
         | 
         | And finally, LYAH is a terrible resource.
        
           | the_af wrote:
           | > And finally, LYAH is a terrible resource.
           | 
           | Could you elaborate? I know LYAH doesn't teach enough to
           | write real programs, and does not introduce necessary
           | concepts such as monad transformers, but why is it so
           | terrible as an introduction to Haskell and FP? (In my mind,
           | incomplete/flawed != terrible... Terrible means "avoid at all
           | costs").
           | 
           | As for your overall point, I remember articles posted here on
           | HN about someone teaching Haskell to children (no prior
           | exposure to any other prog lang) with great success.
        
           | dietlbomb wrote:
           | Is it worth learning JavaScript before learning Elm?
        
       | scotty79 wrote:
       | > 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 wrote:
         | I 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 wrote:
           | > 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.
        
           | scotty79 wrote:
           | > after all, variables are just references
           | 
           | That's the whole point of Rust, that they are not that. They
           | are named, sized memory slots that values can be moved into
           | or moved out of.
           | 
           | > I have definitely seen people struggle to wrap their head
           | around ...
           | 
           | The struggle comes from being forced to use recursion where
           | it doesn't make things easier to express but harder. Then
           | they remember it all compiles down to machine code that just
           | uses iteration fueled by bare metal equivalent of goto-s and
           | the struggle feels pointless.
           | 
           | Imagine somebody took away your stack so when you want to do
           | recursion, you'd be forced to roll your own stack every time.
           | You'd struggle too.
        
       | fbn79 wrote:
       | We need a language with Haskell syntax, Rust memory management
       | and Typescript toolchain/ecosystem :))
        
         | joelthelion wrote:
         | I'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 wrote:
           | You 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.
        
         | swiftcoder wrote:
         | I 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 wrote:
           | Haskell 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.
        
             | swiftcoder wrote:
             | Garbage collectors can really be very efficient in
             | languages that are both strongly-typed and truly immutable.
             | 
             | The type system means you don't have to worry about folks
             | hiding pointers in arbitrary pointer-sized integers, so you
             | know all the roots ahead of time.
             | 
             | Immutability means you can only ever create references in
             | one direction (i.e from new objects to old objects), and
             | you can't ever create cycles.
             | 
             | This lets you do fun shit like a mark&sweep garbage
             | collector in a single pass (rather than the usual two) -
             | and if you have process isolation guarantees (a la Erlang),
             | you don't necessarily have to suspend execution while it
             | runs. Or maybe a generational collector where the
             | generations are entirely implicit.
        
         | empath75 wrote:
         | I think you just want Rust.
         | 
         | You can write extremely haskell-like code with Rust.
         | 
         | Here's the first example in rust:                   fn
         | safe_head<T>(list: &[T]) -> Option<&T> {             match list
         | {                 [first, ..] => Some(first),
         | [] => None,                               }         }
         | fn print_the_first_thing(my_list: &[String]) {
         | match safe_head(my_list) {                 Some(something) =>
         | println!("{}", something),                 None =>
         | println!("You don't have any favourite things? How sad."),
         | }         }              fn main() {             let
         | my_favourite_things = vec!["raindrops on roses".to_string(),
         | "whiskers on kittens".to_string()];             let empty_list:
         | Vec<String> = vec![];
         | print_the_first_thing(&my_favourite_things);
         | print_the_first_thing(&empty_list);         }
         | 
         | (of course Rust has a lot of helper functions that avoid all
         | that verbosity -- you can do the whole thing in one expression,
         | if you want)                   println!(             "{}",
         | my_favourite_things.get(0).map_or(                 "You don't
         | have any favourite things? How sad.".to_string(),
         | |something| something.to_string()             )         );
        
       | Mikhail_K wrote:
       | Haskell 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 wrote:
         | This 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 wrote:
           | > 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 wrote:
             | > 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.
        
             | agentultra wrote:
             | > 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.
             | 
             | I've encountered few C programmers who can predict what
             | instructions will be emitted by their compiler.
             | 
             |  _Update_ : You might be surprised, in the presence of
             | optimizations, how similar the code emitted by gcc and GHC
             | can be for similar programs.
             | 
             | Fewer still those who can specify their pre- and post-
             | conditions and loop invariants in predicate calculus in
             | order prove their implementation is correct.
             | 
             | Most people wing it and rely on past experience or the
             | wisdom of the crowd. What I like to call, _programming by
             | folk lore_. Useful for a lot of tasks, I use it all the
             | time, but it 's not the only way.
             | 
             | The nice thing about Haskell here is that, while there is a
             | lot you cannot prove (termination, etc... please
             | verification friends, understand I'm generalizing here),
             | you can write a sufficient amount of your specification and
             | reason about the correctness of the implementation in the
             | same language.
             | 
             | This has a nice effect: you can write the specification of
             | your algorithm in Haskell. It won't be efficient enough for
             | use at first. However you can usually apply some basic
             | algebra to transform the program you know is correct into
             | one that is performant without changing the meaning of the
             | program.
        
         | kreyenborgi wrote:
         | Haha those issues are things like "LaTeX to HTML conversion:
         | \\\label and \\\ref work for figures but not equations" which I
         | mean how on earth is that related to what language pandoc was
         | created in ... I'm guessing the number of issues are because of
         | pandoc's popularity and insane scope.
        
           | Mikhail_K wrote:
           | So you're saying that pandoc is correct in academic, but not
           | practical sense?
        
             | kreyenborgi wrote:
             | No.
        
       | bwidlar wrote:
       | An implementation of an extended subset of Haskell. It uses
       | combinators for the runtime execution:
       | 
       | https://github.com/augustss/MicroHs
       | 
       | https://www.youtube.com/watch?app=desktop&v=uMurx1a6Zck&t=36...
        
         | tromp wrote:
         | An even more minimal Haskell compiler and combinatory logic
         | runtime won in the 26th IOCCC:
         | 
         | https://crypto.stanford.edu/~blynn/compiler/ioccc.htm
        
       | Coolbeanstoo wrote:
       | I 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 wrote:
         | I 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-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 wrote:
           | > 1. Decide on an effect system
           | 
           | This shouldn't even be proposed as a question to someone new
           | to Haskell. They should learn how monad transformers work and
           | just use them. 90% of developers playing around effect
           | systems would be just fine with MTL or even just concrete
           | transformers. All Haskell effect systems should be considered
           | experimental at this point with unclear shelf lives.
           | 
           | Everything else you said I agree with as solid advice!
        
             | cosmic_quanta wrote:
             | Someone truly new to Haskell shouldn't use it
             | professionally.
             | 
             | Once you've learned what is necessary to, say, modify
             | already-existing applications, you should be familiar with
             | monads and some basic monad transformers like ReaderT.
             | 
             | Once you're there, I don't think 'choosing an effect
             | system' is a perilous question. The monad transformer
             | library, mtl, _is an effect system_ , the second simplest
             | one after IO.
        
               | solomonb wrote:
               | The original poster said they want to use Haskell
               | professionally but that they are struggling to understand
               | how to structure programs.
               | 
               | > Once you're there, I don't think 'choosing an effect
               | system' is a perilous question. The monad transformer
               | library, mtl, is an effect system, the second simplest
               | one after IO.
               | 
               | I'm aware of that, generally when people say "choose
               | effect system" they mean choose some algebraic effect
               | system, all of which (in Haskell) have huge pitfalls. The
               | default should be monad transformers unless you have some
               | exceptional situation.
        
               | cosmic_quanta wrote:
               | I realize I didn't mention monad transformers at all in
               | my original post, I only linked to them!
               | 
               | I should have mentioned that, as you say, monad
               | transformers should be the default effect system choice
               | for 99% of people
        
           | jsbg wrote:
           | This is excellent advice that unfortunately seems to get lost
           | in a lot of Haskell teachings. I learned Haskell in school
           | but until I had to use it professionally I would have never
           | been able to wrap my head around effect systems. I still
           | think that part of Haskell is unfortunate as it can get in
           | the way of getting things done if you're not an expert, but
           | being able to separate pure functions from effectful ones is
           | a massive advantage.
        
         | bedman12345 wrote:
         | I've been working with pure functional languages and custom
         | lisp dialects professionally my whole tenure. You get a whole
         | bag of problems for a very subjective upside. Teams fragment
         | into those that know how to work with these fringe tools and
         | those who don't. The projects using them that I worked on all
         | had trouble with getting/retaining people. They also all had
         | performance issues and had bugs like all other software. You're
         | not missing out on anything.
        
         | mattgreenrocks wrote:
         | I've used Haskell professionally for two years. It is the right
         | pick for the project I'm working on (static analysis). I'm less
         | sold on the overall Haskell ecosystem, tooling, and the overall
         | Haskell culture.
         | 
         | There are still plenty of ways to do things wrong. Strong types
         | don't prevent that. Laziness is a double-edged sword and can be
         | difficult to reason about.
        
         | jerf wrote:
         | People love to talk about the upsides and the fun and what you
         | can learn from Haskell.
         | 
         | I am one of these people.
         | 
         | People are much more reluctant to share what it is that led
         | them to the conclusion that Haskell isn't something they want
         | to use professionally, or something they can't use
         | professionally. It's a combination of things, such as it just
         | generally being less energizing to talk about that, and also
         | some degree of frankly-justified fear of being harassed by
         | people who will argue loudly and insultingly that you just
         | Don't Get It.
         | 
         | I am not one of those people.
         | 
         | I will share the three main reasons I don't even consider it
         | professionally.
         | 
         | First, Hacker News has a stronger-than-average egalitarian
         | streak and really wants to believe that everybody in the world
         | is already a developer with 15 years of experience and expert-
         | level knowledge in all they survey from atop their accomplished
         | throne, but that's not how the real world works. In the real
         | world I work with coworkers who I have to train why in my Go
         | code, a "DomainName" is a type instead of just a string. Then,
         | just as the light bulb goes off, they move on from the project
         | and I get the next junior dev who I have to explain it to. I'm
         | hardly going to get to the point where I have a team of people
         | who are Haskell experts when I'm explaining this basic thing
         | over and over.
         | 
         | And, to be 100% clear, this is not their "fault", because being
         | a junior programmer in 2024 is facing a mountain of issues I
         | didn't face at their age:
         | https://news.ycombinator.com/item?id=33911633 I wasn't expected
         | to know about how to do source control or write everything to
         | be rollback-able or interact with QA, or, well, see linked post
         | for more examples. Haskell is another stack of requirements on
         | top of a modern junior dev that is a _hell_ of an ask. There
         | better be some damn good reasons for me to add this to my
         | minimim-viable developer for a project. I am not expressing
         | contempt for the junior programmers here from atop my own lofty
         | perch; I am encouraging people to have sympathy with them,
         | especially if you also come up in the 90s when it was really
         | relatively easy, and to make sure you don 't spec out projects
         | where you're basically pulling the ladder up after yourself.
         | You need to have an onboarding plan, and "spend a whole bunch
         | of time learning Haskell" is spending a lot of your onboarding
         | plan currency.
         | 
         | Second, while a Haskell program that has the _chef 's kiss_
         | perfect architecture is a joy to work with, it is much more
         | difficult to get there for a real project. When I was playing
         | with Haskell it was a frequent occurrence to discover I'd
         | architected something wrong, and to essentially need to rewrite
         | the whole program, because there is no intermediate functioning
         | program between where I was and where I needed to be. The
         | strength of the type system is a great benefit, but it does not
         | put up with your crap. But "your crap" includes things like
         | being able to rearchitect a system in phases, or partially, and
         | still have a functioning system, and some other things that are
         | harder to characterize but you do a lot of without even
         | realizing it.
         | 
         | I'd analogize it to a metalworker working with titanium. If you
         | need it, you need it. If you can afford it, great. The end
         | result is amazing. But it's a much harder metal to work with
         | for the exact same reason it's amazing. The strength of the end
         | part is directly reflected in the metal resisting you working
         | with it.
         | 
         | I expect at a true expert level you can get over this, but then
         | as per my first point, demanding that all my fellow developers
         | become true experts in this obscure language is taking it up
         | another level past just being able to work in it at all.
         | 
         | Finally, a lot of programming requirements have changed over
         | the years. 10-15 years ago I could feasibly break my program
         | into a "functional core" and an external IO system. This has
         | become a great deal less true, because the baseline requirement
         | for logging, metrics, and visibility have gone up a lot, and
         | suddenly that "pure core" becomes a lot less appealing. Yes, of
         | course, our pure functions could all return logs and metrics
         | and whathaveyou, and sure, you can set up the syntax to the
         | point that it's almost tolerable, but you're still going to
         | face issues where basically everything is now in some sort of
         | IO. If nothing else, those beautiful (Int -> Int -> Int)
         | functions all become (Int -> Int -> LoggingMetrics Int) and now
         | it isn't just that you "get" to use monadic interfaces but
         | you're in the LoggingMetrics monad for _everything_ and the
         | advantages of Haskell, while they do not go away entirely, are
         | somewhat mitigated, because it really wants purity. It puts me
         | halfway to being in the  "imperative monad" already, and makes
         | the plan of just going ahead and being there and programming
         | carefully a lot more appealing. Especially when you combine
         | that with the junior devs being able to understand the
         | resulting code.
         | 
         | In the end, while I still strongly recommend professional
         | programmers spend some time in this world to glean some lessons
         | from it that are much more challenging to learn anywhere else,
         | it is better to take the lessons learned and learn how to apply
         | them back into conventional languages than to try to insist on
         | using the more pure functional languages in an engineering
         | environment. This isn't even the complete list of issues, but
         | they're sufficient to eliminate them from consideration for
         | almost every engineering task. And in fact every case I have
         | personally witnessed where someone pushed through anyhow and
         | did it, it was ultimately a business failure.
        
           | cosmic_quanta wrote:
           | > I'd analogize it to a metalworker working with titanium. If
           | you need it, you need it. If you can afford it, great. The
           | end result is amazing. But it's a much harder metal to work
           | with for the exact same reason it's amazing.
           | 
           | What a beautiful, succinct analogy. I'm stealing this.
        
           | ninetyninenine wrote:
           | > I'd analogize it to a metalworker working with titanium. If
           | you need it, you need it. If you can afford it, great. The
           | end result is amazing. But it's a much harder metal to work
           | with for the exact same reason it's amazing. The strength of
           | the end part is directly reflected in the metal resisting you
           | working with it.
           | 
           | I'd say you missed one of the main points of Haskell and
           | functional programming in general.
           | 
           | The combinator is the most modular and fundamental
           | computational primitive available in programming. When you
           | make a functional program it should be constructed out of the
           | composition of thousands of these primitive with extremely
           | strict separation from IO and multiple layers of abstraction.
           | Each layer is simply composed functions from the layer below.
           | 
           | If you think of fp programming this way. It becomes the most
           | modular most reconfigurable programming pattern in existence.
           | 
           | You have access to all layers of abstraction and within each
           | layer are independent modules of composed combinators. Your
           | titanium is given super powers where you can access the
           | engine, the part, the molecule and the atom.
           | 
           | All the static safety and beauty Haskell provides is actually
           | a side thing. What Haskell and functional programming in
           | general provides is the most fundamental and foundational way
           | to organize your program such that any architectural change
           | only requires you replacing and changing the minimum amount
           | of required modules. Literally the opposite of what you're
           | saying.
           | 
           | The key is to make your program just a bunch of combinators
           | all the way down with an imperative io shell that is as thin
           | as possible. This is nirvana of program organization and
           | patterns.
        
             | jerf wrote:
             | I'm well aware of functional programming as focusing on
             | composition.
             | 
             | One of the reasons you end up with "refactoring the entire
             | program because of some change" is when you discover that
             | your entire composition scheme you built your entire
             | program around is _wrong_ , e.g., "Gee, this effects
             | library I built my entire code base around to date is
             | really nifty but also I can't actually express my needs in
             | it after all". In a conventional language, you just build
             | in the exceptions, and maybe feel briefly sad, but it
             | works. It can ruin a codebase if you let it, but it's at
             | least an option. In Haskell, you have a _much_ bigger
             | problem.
             | 
             | Now filter that back through what I wrote. You want to
             | explain to your junior developer who is still struggling
             | with the concept of using things other than strings why we
             | have to rewrite the entire code base to use raw IO instead
             | of the effects system we were using because it turns out
             | the compilation time went exponential and we can't fix it
             | in any reasonable amount of effort? How happy are they
             | going to be with you after you just spent a whole bunch of
             | time explaining the way to work with the effects system?
             | They're not going to come away with a good impression of
             | either Haskell or you.
        
       | axilmar wrote:
       | My 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 wrote:
         | An example of how to use the io monad for simulations
         | https://benchmarksgame-team.pages.debian.net/benchmarksgame/...
         | It's one of the nicer to read ones I've seen. Still is terrible
         | imo.
        
         | gspr wrote:
         | Use the ST monad? :)
        
         | louthy wrote:
         | In 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-...
        
         | lieks wrote:
         | You... don't. You have to rely on compiler optimizations to get
         | good performance.
         | 
         | Monads are more-or-less syntax sugar. They give you a structure
         | that allows these optimizations more easily, and also make the
         | code more readable sometimes.
         | 
         | But in your example, update returns a new copy of the state,
         | and you map it over a list for each step. The compiler tries to
         | optimize that into in-place mutation.
         | 
         | IMO, having to rely so much on optimization is one of the weak
         | points of the language.
        
           | kreetx wrote:
           | _You do_ , and you'll have to use do destructive updates
           | within either ST or IO monad using their respective single
           | variable or array types. It looks roundabouty, but does do
           | the thing you want and _it is_ fast.
           | 
           | ST and IO are "libraries" though, in the sense that they not
           | special parts of the language, but appear like any other
           | types.
        
         | tikhonj wrote:
         | I mean, Haskell has mutable vectors[1]. You can mutate them in
         | place either in the IO monad or in the ST monad. They
         | fundamentally work the same way as mutable data structures in
         | any other garbage collected language.
         | 
         | When I worked on a relatively simple simulation in Haskell,
         | that's exactly what I did: the individual entities were
         | immutable, but the state of the system was stored in a mutable
         | vector and updated in place. The actual "loop" of the
         | simulation was a stream[2] of events, which is what managed the
         | actual IO effect.
         | 
         | My favorite aspect of designing the system in Haskell was that
         | I could separate out the core logic of the simulation which
         | could mutate the state on each event from observers which could
         | only _read_ the state on events. This separation between logic
         | and pure metrics made the code much easier to maintain,
         | especially since most of the business needs and complexity
         | ended up being in the metrics rather than the core simulation
         | dynamics. (Not to say that this would always be the case, that
         | 's just what happened for this specific supply chain domain.)
         | 
         | Looking back, if I were going to write a more complex
         | performance-sensitive simulation, I'd probably end up with
         | state stored in a bunch of different mutable arrays, which
         | sounds a lot like an ECS. Doing that with base Haskell would be
         | _really_ awkward, but luckily Haskell is expressive enough that
         | you can build a legitimately nice interface on top of the low-
         | level mutable code. I haven 't used it but I imagine that's
         | exactly what apces[3] does and that's where I'd start if I were
         | writing a similar sort of simulation today, but, who knows,
         | sometimes it's straight-up faster to write your own
         | abstractions instead...
         | 
         | [1]:
         | https://hackage.haskell.org/package/vector-0.13.1.0/docs/Dat...
         | 
         | [2]: https://hackage.haskell.org/package/streaming
         | 
         | [3]: https://hackage.haskell.org/package/apecs
        
           | whateveracct wrote:
           | apecs is really nice! it's not without its issues, but it
           | really is a sweet library. and some of its issues are
           | arguably just issues with ECS than apecs itself.
        
         | icrbow wrote:
         | > does Haskell have a special construct that allows for values
         | to be overwritten
         | 
         | Yes and no.
         | 
         | No, the _language_ doesn 't have a special construct. Yes,
         | there are all kinds of mutable values for different usage
         | patterns and restrictions.
         | 
         | Most likely you end up with mutable containers with some space
         | reserved for entity state.
         | 
         | You can start with putting `IORef EntityState` as a field and
         | let the `update` write there. Or multiple fields for state sub-
         | parts that mutate at different rates. The next step is putting
         | all entity state into big blobs of data and let entities keep
         | an index to their stuff inside that big blob. If your entities
         | are a mishmash of data, then there's `apecs`, ECS library that
         | will do it in AoS way. It even can do concurrent updates in STM
         | if you need that.
         | 
         | Going further, there's `massiv` library with integrated task
         | supervisor and `repa`/`accelerate` that can produce even faster
         | kernels. Finally, you can have your happy Haskell glue code and
         | offload all the difficult work to GPU with `vulkan` compute.
        
           | icrbow wrote:
           | > ECS library that will do it in AoS way
           | 
           | TLAs aren't my forte. It's SoA of course.
        
         | contificate wrote:
         | I have a rather niche theory that many Hindley-Milner type
         | inference tutorials written by Haskellers insist on teaching
         | the error-prone, slow, details of algorithm W because otherwise
         | the authors would need to commit to a way to do destructive
         | unification (as implied by algorithm J) that doesn't attract
         | pedantic criticism from other Haskellers.
         | 
         | For me, I stopped trying to learn Haskell because I couldn't
         | quite make the jump from writing trivial (but neat) little
         | self-contained programs to writing larger, more involved,
         | programs. You seem to need to buy into a contorted way of
         | mentally modelling the problem domain that doesn't quite pay
         | off in the ways advertised to you by Haskell's proponents (as
         | arguments against contrary approaches tend to be hyperbolic).
         | I'm all for persistent data structures, avoiding global state,
         | monadic style, etc. but I find that OCaml is a simpler,
         | pragmatic, vehicle for these ideas without being forced to bend
         | over backwards at every hurdle for limited benefit.
        
         | throwaway81523 wrote:
         | Well what kind of values and how many updates? You might have
         | to call an external library to get decent performance, like you
         | would use NumPy in Python. This might be of interest:
         | https://www.acceleratehs.org/
        
         | mrkeen wrote:
         | > My question for Haskellers is how to do updates of values on
         | a large scale, let's say in a simulation.
         | 
         | The same way games do it. The whole world, one frame at a time.
         | If you are simulating objects affected by gravity, you _do not_
         | recalculate the position of each item in-place before moving
         | onto the next item. You figure out _all_ the new accelerations,
         | velocities and positions, and then apply them all.
        
         | tome wrote:
         | I'm not sure why you say not to respond with 'use the IO monad'
         | because that's exactly how you'd do it! As an example, here's
         | some code that updates elements of a vector.
         | import Data.Vector.Unboxed.Mutable                  import
         | Data.Foldable (for_)         import Prelude hiding (foldr,
         | read, replicate)                  -- ghci> main         --
         | [0,0,0,0,0,0,0,0,0,0]         -- [0,5,10,15,20,25,30,35,40,45]
         | main = do           v <- replicate 10 0
         | printVector v                    for_ [1 .. 5] $ \_ -> do
         | for_ [0 .. 9] $ \i -> do               v_i <- read v i
         | write v i (v_i + i)                    printVector v
         | printVector :: (Show a, Unbox a) => MVector RealWorld a -> IO
         | ()         printVector v = do           list <- foldr (:) [] v
         | print list
         | 
         | It does roughly the same as this Python:                   #
         | python /tmp/test28.py         # [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
         | # [0, 5, 10, 15, 20, 25, 30, 35, 40, 45]         def main():
         | v = [0] * 10                      print(v)
         | for _ in range(5):                 for i in range(10):
         | v_i = v[i]                     v[i] = v_i + i
         | print(v)                  if __name__ == '__main__': main()
        
         | rebeccaskinner wrote:
         | > 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?
         | 
         | You don't _have to_ recreate the list each time, but that's
         | probably where I'd suggest starting. GHC is optimized for these
         | kinds of patterns, and in many cases it'll compile your code to
         | something that does in-place updates for you, while letting you
         | write pure functions that return a new list. Even when it
         | can't, the runtime is designed for these kinds of small
         | allocations and updates, and the performance is much better
         | than what you'd get with that kind of code in another language.
         | 
         | If you decided that you really did need in-place updates, then
         | there are a few options. Instead of storing a vector of values
         | (if you are thinking about performance you probably want
         | vectors instead of lists), you can store a vector of references
         | that can be updated. IO is one way to do that (with IORefs) but
         | you can also get "internal mutability" using STRefs. ST is
         | great because it lets you write a function that uses mutable
         | memory but still looks like a pure function to the callers
         | because it guarantees that the impure stuff is only visible
         | inside of the pure function. If you need concurrency, you might
         | use STM and store them as MVars. Ultimately all of these
         | options are different variations on "Store a list of pointers,
         | rather than a list of values".
         | 
         | There are various other optimizations you could do too. For
         | example, you can use unboxed mutable vectors to avoid having to
         | do a bunch of pointer chasing. You can use GHC primitives to
         | eek out even better performance. In the best case scenario I've
         | seen programs like this written in Haskell be competitive with
         | Java (after the warmup period), and you can keep the memory
         | utilization pretty low. You probably won't get something that's
         | competitive with C unless you are writing extremely optimized
         | code, and at that point most of the time I'd suggest just
         | writing the critical bits in C and using the FFI to link that
         | into your program.
        
         | kccqzy wrote:
         | I don't understand why you hate the IO monad so much. I mean
         | I've seen very large codebases doing web apps and almost
         | everything is inside the IO monad. It's not as "clean" and not
         | following best practices, but still gets the job done and is
         | convenient. Having pervasive access to IO is just the norm in
         | all other languages so it's not even a drawback.
         | 
         | But let's put that aside. You can instead use the ST monad (not
         | to be confused with the State monad) and get the same
         | performance benefit of in-place update of values.
        
         | whateveracct wrote:
         | You can use apecs, a pretty-fast Haskell ECS for those sorts of
         | things.
        
       | kreyenborgi wrote:
       | I'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 wrote:
         | _> 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 wrote:
       | Using "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 wrote:
         | data 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 wrote:
           | Yes, 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.
        
         | magicalhippo wrote:
         | I don't use Haskell, so this is a dumb question. Why can't X be
         | implicitly cast to Maybe X?
        
           | tobz619 wrote:
           | It can using pure or return or if working with just Maybe
           | specifically then Maybe is defined like so:
           | 
           | data Maybe a = Just a | Nothing
           | 
           | So to make an X a Maybe X, you'd put a Just before a value of
           | type X.
           | 
           | For example:
           | 
           | one :: Int
           | 
           | one = 1
           | 
           | mOne :: Maybe Int
           | 
           | mOne = Just one -- alternatively, pure one since our type
           | signature tells us what pure should resolve to.
           | 
           | Reason we can do this is because Maybe is also an Applicative
           | and a Monad and so implements pure and return which takes an
           | ordinary value and wraps it up into an instance of what we
           | want.
        
             | cubefox wrote:
             | Isn't that explicit casting? Implicit casting would be
             | automatically performed by the compiler without the need to
             | (re)write any explicit code.
        
             | ninkendo wrote:
             | Sounds similar to how you need to do Some(x) when passing x
             | to something expecting an Option in rust.
             | 
             | Swift interestingly doesn't require this, but only because
             | Optionals are granted lots of extra syntax sugar in the
             | language. It's really wrapping it in .some(x) for you
             | behind the scenes, but the compiler can figure this out on
             | its own.
             | 
             | This means that in swift, changing a function from f(T) to
             | f(T?) (ie. f(Optional<T>)) is a source-compatible change,
             | albeit not an ABI-compatible one.
        
             | magicalhippo wrote:
             | > mOne = Just one
             | 
             | I'd call that explicit casting. Implicit casting would be
             | mOne = one
             | 
             | Compiler already knows what "one" is, it could insert the
             | "Just" itself, no? Possibly due to an operator defined on
             | Maybe that does this transformation?
             | 
             | That is, are there some technical reasons it doesn't?
             | 
             | Or is it just (no pun inteded) a language choice?
        
           | mrkeen wrote:
           | Haskell/GHC tells you what the types are. Proper, global,
           | most-general type inference. Not that local inference crap
           | [1] that the sour-grapes types will say is better.
           | 
           | You lose this ability if you start letting the compiler
           | decide that `Int` is as good as `Maybe Int`. Or if an `Async
           | (Async String)` may as well be an `Async String`.
           | 
           | That's not to say it's not easy to transform (just a few
           | keystrokes), but explicit beats implicit when it comes to
           | casting (in any language).
           | 
           | [1] Does this work?                 var x = 1 == 2 ?
           | Optional.of(2L) : Optional.empty().map(y -> y + y);       //
           | Operator '+' cannot be applied to 'java. lang. Object',
           | 'java. lang. Object'
           | 
           | How about                 Optional<Long> x = 1 == 2 ?
           | Optional.of(2L) : Optional.empty().map(y -> y + y);       //
           | Operator '+' cannot be applied to 'java. lang. Object',
           | 'java. lang. Object'
           | 
           | or even                 Optional<Long> x = 1 == 2 ?
           | Optional.of(2L) : Optional.empty().map((Long y) -> y + y);
           | // Cannot infer functional interface type
           | 
           | No, we needed Optional.<Long>empty() instead of
           | Optional.empty() to make it work
        
             | magicalhippo wrote:
             | > letting the compiler decide that `Int` is as good as
             | `Maybe Int`
             | 
             | I was thinking more like explicitly telling the compiler
             | that an implicit cast is OK, in other languages done by
             | implementing the implicit cast operator for example.
             | 
             | edit: but if I understood you correctly, Haskell just
             | doesn't support any implicit casting?
        
               | mrkeen wrote:
               | It will do some wrangling of literals for you, as long as
               | it can unambiguously decide on an exact type during type-
               | checking.
               | 
               | If no other info is given, it will treat `3 + 3` as
               | Integer + Integer (and emit a compiler warning because it
               | guessed the type).
               | 
               | With `(3 :: Int64) + 3`, the right 3 will resolve to
               | Int64. Same if you swap their positions.
               | 
               | `(3 :: Int64) + (3 :: Int32)` is a compile error.
               | 
               | "Text literals" can become a String, a Text, or a
               | ByteString if you're not explicit about it.
               | 
               | > implicit cast operator
               | 
               | Wouldn't that make it explicit?
        
               | magicalhippo wrote:
               | > Wouldn't that make it explicit?
               | 
               | No, the casting is still done implicitly. That is I can
               | make the following compile fine in Delphi if I add an
               | implicit cast operator to either Foo or Bar:
               | Foo x := Foo.Create();         Bar y := x;
               | 
               | If neither of them have a suitable implicit cast operator
               | defined, it will of course fail to compile.
               | 
               | Just an example, nothing unique about Delphi. You can see
               | an example of the operator definition here[1].
               | 
               | [1]: https://docwiki.embarcadero.com/RADStudio/Alexandria
               | /en/Oper...
        
         | tobz619 wrote:
         | If you have a value "aValue :: a" and a monadic function of
         | "mFunc :: (a -> Maybe b)" that's essentially just asking you to
         | use `>>= :: Maybe a -> (a -> Maybe b) -> Maybe b` as well as
         | `pure :: Applicative f => a -> f a` which will lift our regular
         | `aValue` to a `Maybe a` in this instance.
         | 
         | Then to get the result "b" you can use the `maybe :: b -> (a ->
         | b) -> Maybe a -> b` function to get your "b" back and do the
         | weakening as you desire.
         | 
         | `Maybe` assumes a computation can fail, and the `maybe`
         | function forces you to give a default value in the case that
         | your computation fails (aka returns Nothing) or a
         | transformation of the result that's of the same resultant type.
         | 
         | Overall, you'd end up with a function call that looks like:
         | 
         | foo :: b
         | 
         | foo = maybe someDefaultValueOnFailure someFuncOnResult (pure
         | aValue >>= mFunc)
         | 
         | or if you don't want to change the result then you can use
         | `fromMaybe :: a -> Maybe a -> a`
         | 
         | bar :: b
         | 
         | bar = fromMaybe someOtherDefaultValueOnFailure (pure value >>=
         | mFunc) -- if the last computation succeeds, return that value
         | of resultant type of your computation
        
           | cubefox wrote:
           | Perhaps that theoretically solves the problem, but it sounds
           | awfullly complicated in practice.
        
           | HelloNurse wrote:
           | This is fine and understandable in theory, but a usability
           | disaster in practice.
           | 
           | If function f returns b or nothing/error, and is then
           | improved to return b always, client code that calls f should
           | not require changes or become invalid, except perhaps for a
           | dead code warning on the parts that deal with the now
           | impossible case of a missing result from f.
           | 
           | You are suggesting not only a pile of monad-related ugly
           | complications to deal with the mismatch between b and Maybe
           | b, which are probably the best Haskell can do, but also
           | introducing default values that can only have the practical
           | effect of poisoning error handling.
        
             | the_af wrote:
             | > If function f returns b or nothing/error, and is then
             | improved to return b always, client code that calls f
             | should not require changes or become invalid
             | 
             | Why do you need to change the type signature at all? You
             | "improved" [1] a function to make impossible for the error
             | case to occur, but it's used everywhere and the calling
             | code must handle the error case (I mean, that's what static
             | typing of this sort). So there you have it: the client code
             | is not rendered invalid, it just has dead code for handling
             | a case that will never happen (or more usually, this just
             | bubbles up to the error handler, not even requiring dead
             | code at every call site).
             | 
             | As an aside, I don't see the problem with the "pile of
             | monads" and it doesn't seem very complicated.
             | 
             | ----
             | 
             | [1] which I assume means "I know I'll be calling this with
             | values that make it impossible for the error to occur". If
             | you are actually changing the code, well, it goes without
             | saying that if the assumptions you made when choosing the
             | type changed when re-writing the function, well... the
             | calling sites breaking everywhere is a _strength_ of static
             | typing.
        
               | HelloNurse wrote:
               | Changing the type signature (which, by the way, could be
               | at least in easy cases implicitly deduced by the compiler
               | rather than edited by hand) allows new client code to
               | assume that the result is present.
        
               | cubefox wrote:
               | (or absent in the case of input parameters)
        
               | the_af wrote:
               | A function in which the input is needed for the
               | computation is very different to one where it's not
               | needed. I would expect the type signature to reflect
               | this, why would you want it otherwise?
        
               | cubefox wrote:
               | Say you have a function which expects objects of type Foo
               | as an input and which returns objects of type Baz. One
               | day, the function is improved by also accepting the type
               | Bar, i.e. Foo|Bar. So Foo isn't needed for the
               | computation, because Bar is also accepted.
               | 
               | Or you have a function which expects objects of type
               | String as an input. But then you realize that in your
               | case, null values can be handled just like empty strings.
               | So the input type can be relaxed to String|Null.
        
               | the_af wrote:
               | Changing the type signature to relax/strengthen pre or
               | post conditions is a fundamental change though. I would
               | expect it to break call sites. That's a feature!
        
               | HelloNurse wrote:
               | Strengthening postconditions and relaxing preconditions
               | is harmless in theory, so it should be harmless in
               | practice.
               | 
               | Haskell gets in the way by facilitating clashes of
               | incompatible types: there are reasons to make breaking
               | changes to type signatures that in more deliberately
               | designed languages might remain unaltered or compatible,
               | without breaking call sites.
        
         | kyjasdfwus wrote:
         | Scala seems to be the only language that recognizes the merit
         | of having both unions and ADTs. It even has GADTs!
        
           | tobz619 wrote:
           | Haskell also has GADTs: https://ghc.gitlab.haskell.org/ghc/do
           | c/users_guide/exts/gadt..., firstly as an extension and now
           | it's built into the latest version of this year's compiler
        
             | kyjasdfwus wrote:
             | Yep, Scala is influenced by Haskell.
        
             | iso8859-1 wrote:
             | You're misinterpreting 'GHC2024'. It's just a language
             | edition, a short hand of enabling a bunch of extensions.
             | You have been able to enable GADTs for many years now, with
             | just a single pragma. It has been built in to GHC for all
             | these years.
        
           | cubefox wrote:
           | Yet ironically Scala is not null safe I believe.
        
             | vips7L wrote:
             | https://docs.scala-
             | lang.org/scala3/reference/experimental/ex...
        
               | cubefox wrote:
               | Opt-in is better than nothing, but in practice I assume
               | this sees little use because it breaks compatibility with
               | old code. Null safety (and type safety in general) has to
               | be present in a programming language from the start; it
               | can't realistically be added as a feature later.
        
               | vips7L wrote:
               | I don't think I agree. C# added it and its gone well and
               | Java is adding null safety without breaking backwards
               | compatibility at all.
        
               | cubefox wrote:
               | Types in old code may be nullable, or not, the compiler
               | doesn't know, so the only way the compiler can ensure
               | null safety for using old code is by enforcing you to do
               | null checks everywhere. That's not very practical.
               | Moreover, the old code itself may still produce null
               | pointer exceptions.
        
               | vips7L wrote:
               | But that doesn't break compatibility like you claimed and
               | it also doesn't support your conclusion that it will not
               | likely be used.
               | 
               | > he compiler doesn't know, so the only way the compiler
               | can ensure null safety for using old code is by enforcing
               | you to do null checks everywhere
               | 
               | This isn't necessarily true. Java's approach is to have 3
               | types: nullable, non-null, and platform (just like
               | Kotlin). Platform types are types without nullness
               | markers and don't require strict null checks to prevent
               | backwards compatibility breaking. Yes, old code may still
               | product null pointers, but we don't need 100% correctness
               | right away. At some point platform types will be 1% of
               | code in the wild rather than 100%.
        
               | cubefox wrote:
               | > At some point platform types will be 1% of code in the
               | wild rather than 100%.
               | 
               | In the case of Java this could take decades. Or people
               | simply continue to write platform types because they are
               | lazy (the compiler doesn't force them). Then platform
               | types will never decrease substantially.
        
               | vips7L wrote:
               | I don't think that's true and I don't think there is any
               | data to back that up. We've already seen in the C#
               | community rapid adoption of nullness markers. This whole
               | goal post moving and the idea that if we can't have 100%
               | we shouldn't do it at all is a bit exhausting so I think
               | I'm done here. Cheers man.
        
               | valenterry wrote:
               | Well yeah, there is no way around this on the JVM. That's
               | one of the JVM's problems/drawbacks. Everything can be
               | null and everything can throw exceptions.
               | 
               | But in practice, as long as you use only Scala libraries
               | and are careful when using Java libraries it's not really
               | an issue. (speaking as a professional Scala developer for
               | many many years)
        
               | neonsunset wrote:
               | If anything, it's a default to have them on in any
               | reasonably recent project - you get that with all
               | templates, default projects, etc. Actively maintained
               | libraries are always expected to come with NRT support.
               | If this is not the case, it's usually a sign the library
               | is maintained by developers who ignore the
               | conventions/guidelines and actively went out of their way
               | to disable them, which usually a strong signal to either
               | file an issue or completely avoid such library.
               | 
               | Similar logic applies to code that has migrated over to
               | .NET 6/8, or any newly written code past ~.NET 5.
        
         | pyrale wrote:
         | > 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).
         | 
         | If your value of Y is predicated on receiving an X, I have
         | trouble seeing how you would write such a function. If you have
         | a default value, then Haskell would solve it just like any
         | language with optionals:                   y :: Int -> String
         | (fromMaybe "defaultValue" (map y (Just 3))
         | 
         | > Several other null-safe languages [...] returning Y implies
         | returning Y or Null.
         | 
         | I have trouble seeing how the language is null-safe in that
         | situation.
        
           | cubefox wrote:
           | > If your value of Y is predicated on receiving an X
           | 
           | We didn't assume it is. Say you have a function of type
           | (String -> String|Null). Further assume that you realize you
           | don't necessarily need a String as input, and that you in
           | fact are able to always output a string, no matter what. This
           | means you can rewrite (improve!) the function such that it
           | now has the type (String|Null -> String). Relaxing the type
           | requirements for your inputs, or strengthening the guarantees
           | for the type of your output, or both, is always an
           | improvement. And there is no logical reason why you would
           | need to change any external code for that. But many type
           | systems are not able to automatically recognize and take
           | advantage of this logical fact.
           | 
           | > > Several other null-safe languages [...] returning Y
           | implies returning Y or Null.
           | 
           | > I have trouble seeing how the language is null-safe in that
           | situation.
           | 
           | If you always assign a value of type Y to a variable of type
           | Y|Null, the compiler will enforce a check for Null if you
           | access the value of the variable, which is unnecessary (as
           | the type of the variable could be changed to Y), but it can't
           | result in a null pointer exception.
        
             | pyrale wrote:
             | The mainstream is languages that will happily accept null
             | as anything, and crash at runtime. Sure, union types are
             | cool, but they aren't expressible in most languages, while
             | the optional construct is.
             | 
             | Haskell's type system definitely is a positive example of
             | what can be done to avoid completely the null problem. Is
             | it the utmost that can be done? No. But it's been a working
             | proof of solution for 20 years, while proper typecheckers
             | for union types are a recent thing.
        
               | cubefox wrote:
               | Yeah but there are arguably different standards for
               | Haskell. Haskell's advanced type system is one of its
               | main selling points, so it doesn't make sense to explain
               | the benefits of Haskell with a case (Maybe) where its
               | type system falls short (no "or" in types).
        
               | pyrale wrote:
               | > Haskell's advanced type system is one of its main
               | selling points, so it doesn't make sense to explain the
               | benefits of Haskell with a case where it's type system
               | falls short.
               | 
               | Falls short compared to what? Arguably, if you're talking
               | to someone using Java or Python, Maybe is plenty enough;
               | and getting started on type families is certainly not
               | going to work well.
        
               | cubefox wrote:
               | These languages don't have null safety. Haskell does have
               | null safety, but at the cost of the additional complexity
               | that comes with Maybe wrapping. So it's not as
               | unambiguously an improvement as union typing is (which
               | adds less complexity but still grants null safety).
        
             | kreetx wrote:
             | Don't mean to appear as talking down to you, but the
             | "relaxation" or "strengthening" that you talk about exactly
             | corresponds to either (1) changing the function that you
             | use at the call site, or (2) changing the "external code"
             | function. The thing you call "improvement" sounds like a
             | plain type error to me.
        
               | cubefox wrote:
               | Then you are misunderstanding something...
        
               | gtf21 wrote:
               | Yeah I also really don't understand the point that's
               | being made here: it looks like a great way to introduce
               | more errors.
        
             | akritid wrote:
             | This came to mind while considering your interesting point:
             | After such a change, wouldn't you feel the urge to inspect
             | all users of the stricter return type and remove
             | unnecessary handling of a potential null return?
        
               | cubefox wrote:
               | I don't know about such urges. But sometimes there is no
               | possibility to inspect all user code, e.g. when you are
               | providing a library or API function.
        
             | itishappy wrote:
             | > Several other null-safe languages [...] returning Y
             | implies returning Y or Null.
             | 
             | If `Y` is implicitly `Y|Null`, then it is no longer
             | possible to declare "this function does not return null" in
             | the type system. Now understanding what a function can
             | return requires checking the code or comments. This is the
             | opposite of null safe.
        
               | cubefox wrote:
               | > If `Y` is implicitly `Y|Null`
               | 
               | It isn't. It's just that if you say "this function
               | returns Y or null", and it returns Y, your statement was
               | true. If you give me a hammer, this implies you gave me a
               | hammer or a wrench.
        
               | itishappy wrote:
               | It _must_. If it is possible to rewrite `X - > Y|Null` as
               | `X|Null -> Y` without changes to external code, then the
               | `X` type needs to accept `X|Null` and the `Y` type needs
               | to accept `Y|Null`, therefore any `T` must implicitly be
               | `T|Null` and the language is not null safe. Result types
               | are what you get when you require explicit conversions.
               | 
               | I may still be thinking about this incorrectly. Do you
               | have an language in mind that contradicts this?
        
               | cubefox wrote:
               | You seem to think `X -> Y|Null` and `X|Null -> Y` have to
               | be equivalent, but that's not the case. The second
               | function type has a more general input type and a more
               | restricted return type. And a function which can accept X
               | as an input can be replaced with a function that can
               | accept X or Null (or X or anything else) as input type.
               | And a function which has can return types Y or Null (or Y
               | or anything else) can be replaced with a function that
               | can return type Y. Old call site code will still work. Of
               | course this replacement only makes sense if it is
               | possible to improve the function in this way from a
               | perspective of business logic.
               | 
               | > I may still be thinking about this incorrectly. Do you
               | have an language in mind that contradicts this?
               | 
               | Any language which supports "union types" of this sort,
               | e.g. Ceylon or, nowadays, Typescript.
        
               | itishappy wrote:
               | I get it! (Thanks, playing around with actual code helped
               | a ton.) For example, in Typescript you're saying you can
               | add a default value simply:                   # old
               | function f(x: number): number {             return 2 * x;
               | }              # new         function f(x: number|null):
               | number {             x = x || 0;             return 2 *
               | x;         }              # usage         # old
               | f(2)         # new         f(2) # still works!
               | 
               | But in Haskell this requires changing the call sites:
               | -- old         f :: Int -> Int         f = (*2)
               | -- new         f :: Maybe Int -> Int         f = maybe 0
               | (*2)              -- usage         -- old         f 2
               | -- new         f (Just 2) -- different!
               | 
               | But I actually feel this is an antipattern in Haskell
               | (and TypeScript too), and a separate wrapper function
               | avoids refactoring while making things even more user
               | friendly.                   -- old         f :: Int ->
               | Int         f = (*2)              -- new         fMaybe
               | :: Maybe Int -> Int         fMaybe = maybe 0 f
               | -- usage         -- old         f 2         -- new
               | f 2 -- still works!         fMaybe Nothing -- works too!
               | 
               | Here's some wrappers for general functions (not that
               | they're needed, they're essentially just raw prelude
               | functions):                   maybeOut :: (a -> b) -> (a
               | -> Maybe b)         maybeOut = fmap Just
               | maybeIn :: b -> (a -> b) -> (Maybe a -> b)
               | maybeIn = maybe              maybeBoth :: b -> (a -> b)
               | -> (Maybe a -> Maybe b)         maybeBoth d = maybeOut .
               | maybeIn d
               | 
               | Added bonus, this approach avoids slowing down existing
               | code with the null checks we just added.
        
         | solomonb wrote:
         | So in your hypothetical language with union types `X | Null ->
         | Y` is a function that can actually return `Y | Null`? Why would
         | you want to allow that as an implicit behavior? This would make
         | for surprising and unclear error handling requirements.
         | 
         | One of the main points of encoding error information in the
         | type system in the type system is that it forces you to account
         | for it when you modify your code.
         | 
         | By "weakening" your assumptions on your function to allow it to
         | produce Null values you have introduced a new requirement at
         | all your call sites. Everywhere that you call this function now
         | needs to handle the Null value. Its a GOOD thing that Haskell
         | forces you to handle this via the type system.
        
           | cubefox wrote:
           | > So in your hypothetical language with union types `X | Null
           | -> Y` is a function that can actually return `Y | Null`?
           | 
           | No, but a function which returns Y | Null can be replaced
           | with a function which returns Y without changing code on the
           | call site.
           | 
           | Imagine I always used to give you a nail (Y) or nothing
           | (Null), and you can handle both receiving a nail and
           | receiving nothing. Then I can, at any time, change my
           | behavior to giving you nails only. Because you can already
           | handle nails, and the fact that I now never give you nothing
           | doesn't bother you. You just perform a (now useless) check of
           | whether you have received a nail or nothing.
        
             | solomonb wrote:
             | > a function which returns Y | Null can be replaced with a
             | function which returns Y without changing code on the call
             | site.
             | 
             | Yes this falls out of injectivity.
             | 
             | > the function type (X -> Y|Null) can be improved by
             | rewriting the function to (X|Null -> Y)
             | 
             | I agree that any value received by the former function
             | (`X`) can be received by the latter function (`X|Null`).
             | However you cannot rewrite the former to have the signature
             | of the latter.
             | 
             | You would need to write:
             | 
             | prf : (X -> Y|Null) -> X|Null -> Y
             | 
             | You would have to be able to convert a `Null` value into a
             | `Y`.
             | 
             | You could definitely use `X|Null -> Y` to implement `X ->
             | Y|Null` but that is not what you are claiming.
        
       | robertlagrant wrote:
       | I, 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 wrote:
         | > - 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 wrote:
           | Thank 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 wrote:
             | Oh! 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).
        
             | gtf21 wrote:
             | For reference (in case it's helpful), my website (where
             | this essay is hosted) is written in Haskell and is
             | basically a fairly simple webserver.
             | 
             | For the "hello world" webserver, this might be a bit
             | instructive: https://github.com/gfarrell/gtf.io/blob/main/s
             | rc/GTF/Router....
        
         | kccqzy wrote:
         | You can't do any of that without having first understood a
         | bottom-up introduction. There are so many web frameworks from
         | Yesod to Scotty to Servant (these are just the ones I've used
         | personally) but you can't use any of them without at least an
         | understanding of the language.
        
         | Ericson2314 wrote:
         | https://haskell-beam.github.io/beam/ is fantastic, but good
         | luck understanding it if you don't already know some Haskell
        
         | justinhj wrote:
         | That sounds valuable too but maybe it comes after the basic
         | concepts or you may find people immediately dismiss it. There
         | is all kinds of extra syntax and baggage that may seem
         | pointless at first.
        
         | valenterry wrote:
         | This doesn't work.
         | 
         | Imagine you talk to someone who has done assembly his whole
         | life and now wants to write something in, let's say, Java.
         | 
         | What would you think if he asks the question in the way you
         | did?
         | 
         | Sometimes, when you learn a language that is _so different_ you
         | really really should NOT try to assume that your current
         | knowledge just translates.
        
       | 0xTJ wrote:
       | While 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 wrote:
         | Sorry 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 wrote:
           | Fair 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 wrote:
             | The 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 wrote:
       | I 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 wrote:
         | That'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 wrote:
           | > 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 wrote:
           | > 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.
        
           | mrkeen wrote:
           | > Could you imagine people saying the issue with Java is its
           | extremism towards objects and method calls?
           | 
           | "Execution in the Kingdom of Nouns" comes to mind.
           | 
           | https://steve-yegge.blogspot.com/2006/03/execution-in-
           | kingdo...
        
         | the_af wrote:
         | > _I feel like part of the problem is Haskell 's extremism
         | towards purity and immutability_
         | 
         | You missed "lazy evaluation by default" in that list ;) Those
         | properties are kind of the definition of Haskell, so without
         | all of them you'd have another language.
         | 
         | Like the sibling commenter mentions, this seems more of a "I'm
         | unfamiliar with this" thing rather than a problem with
         | Haskell...
        
         | cosmic_quanta wrote:
         | > I find some code easier to express with procedural/mutable
         | loops than recursion, and I believe the vast majority of
         | programmers
         | 
         | I think this comes from early and continuous exposure to some
         | forms of programming, rather than inherent to pure functional
         | programming.
         | 
         | I personally find it much easier to use recursion and other
         | functional techniques because it composes better. This probable
         | comes from my exposure to Haskell much earlier than most.
        
         | cies wrote:
         | This has been researched. If you were educated with FP-langs
         | first, you'd say it's harder "to express with
         | procedural/mutable loops".
         | 
         | > I believe the vast majority of programmers.
         | 
         | Yups. We get educated with imperative langs. The majority of us
         | do.
        
         | enugu wrote:
         | Haskell doesn't stop you from mutation, it just makes you
         | explicitly mark it, like types of inputs/output are explicit in
         | statically typed languages instead of being implicit or
         | borrowing is explicit in Rust.
         | 
         | Mutations becomes first class values like numbers or arays, and
         | hence can be primitives for more complex mutations, whose types
         | can be inferred from the types of primitive mutations.
         | 
         | This means that the we have compile time guarantees that
         | certain piece of code wont change anything in certain part of
         | the state - This function wont change anything in that part of
         | the data.
         | 
         | It is no joke, though not strictly true, that Haskell has been
         | called the world's best imperative language.
        
         | gtf21 wrote:
         | > I find some code easier to express with procedural/mutable
         | loops than recursion
         | 
         | This is what I was talking about in the section "Unlearning and
         | relearning". While there are _some_ domains (like embedded
         | systems) for which Haskell is a poor fit, a lot of the
         | difficulties people have with it (and with FP in general) is
         | that they have been heavily educated to think in a particular
         | way about computation. That's an accident of history, rather
         | than any fundamental issue with the programming paradigm.
        
         | wavemode wrote:
         | > I feel like part of the problem is Haskell's extremism
         | towards purity and immutability
         | 
         | Eh. Elm has achieved quite a bit of success just by having good
         | tooling and a good ecosystem. Says something about people's
         | willingness to learn pure functional programming, if the
         | situation is right.
         | 
         | > I find some code easier to express with procedural/mutable
         | loops than recursion
         | 
         | This is usually a familiarity problem, IMO.
         | 
         | I often say: people think mastering Haskell is about mastering
         | monads. But it's actually about mastering folds.
        
           | 1-more wrote:
           | the best intro to writing Haskell for me was writing Elm. The
           | best intro to writing Elm was writing pointfree Codewars kata
           | solutions in JS using
           | with(require("ramda")) fn = pipe(...)
           | 
           | And yep, you end up with a lot of folds (well, reduces) where
           | that ellipsis is. Or related functions that are really folds.
           | 
           | For those wondering `with` in JS is a bit like `extract` in
           | PHP except it creates a new context right after itself rather
           | than modify the context it finds itself in. It's super
           | deprecated because it's inscrutable and silly except in this
           | case and/or when you need to solve Codewars katas in a
           | limited number of characters.
           | 
           | https://developer.mozilla.org/en-
           | US/docs/Web/JavaScript/Refe...
           | 
           | https://www.php.net/manual/en/function.extract.php
           | 
           | EDIT ramda is a nice utility library in JS that supports
           | partial application of arguments in arbitrary order
           | https://ramdajs.com/docs/#subtract
        
       | imoverclocked wrote:
       | My 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 wrote:
         | And also horrible for the typical functional programmer that
         | likes clever "solutions" but hates the "boring" parts of actual
         | good software.
        
         | gtf21 wrote:
         | I 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?
        
           | weebull wrote:
           | I 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.
        
           | YuukiRey wrote:
           | You wrote
           | 
           | > Haskell solves the problem of the representation of
           | computations which may fail very differently: explicitly
           | through the type system.
           | 
           | In my experience this is very hit and miss. Some libraries
           | use exceptions for lots of error states that in Go would be a
           | returned error value. I'm therefore left to decipher the docs
           | (which are often incomplete) to understand which exceptions I
           | can except and why and when.
           | 
           | Last library I remember is
           | https://hackage.haskell.org/package/modern-uri
           | 
           | From their docs: > If the argument of mkURI is not a valid
           | URI, then an exception will be thrown. The exception will
           | contain full context and the actual parse error.
           | 
           | The pit of success would be if every function that can fail
           | because of something reasonable (such as a URI parser for
           | user supplied input) makes it a compile time message
           | (warning, error, whatever you prefer) if I fail to consider
           | the error case. But there's nothing that warns me if I fail
           | to catch an exception, so in the end, in spite of all of
           | Haskell's fancy type machinery, in this case, I'm worse off
           | than in Golang.
        
       | Vosporos wrote:
       | At 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 wrote:
       | Not 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 wrote:
         | Nope: 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 wrote:
         | You don't even notice the laziness most of the time. Even if
         | you are benefiting from it.
        
       | dbacar wrote:
       | Why not?
        
       | iso8859-1 wrote:
       | > 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
       | 
       | [1]: Cheng Shao, the Wasm backend for GHC:
       | https://www.youtube.com/watch?v=uMurx1a6Zck&t=13290s
        
         | samvher wrote:
         | For 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/
        
           | iso8859-1 wrote:
           | Lean aims to be a general purpose language, but I haven't
           | seen people actually write HTTP servers in it. If Leo de
           | Moura really wanted it to be general purpose, what does the
           | concurrent runtime look like then? To my knowledge, there
           | isn't one?
           | 
           | That's why I've been writing an HTTP server in Idris2
           | instead. Here's a todo list demo app[1] and a hello world
           | demo[2]. The advantage of Idris is that it compiles to e.g.
           | Racket, a high level language with a concurrent runtime you
           | can bind to from Idris.
           | 
           | It's also interesting how languages don't need their own
           | hosting (e.g. Hackage) any more. Idris packages are just
           | listed in a TOML file[3] (like Stackage) but still hosted on
           | GitHub. No need for versions, just use git commit hashes.
           | It's all experimental anyway.
           | 
           | [1]: https://janus.srht.site/docs/todolist.html [2]:
           | https://git.sr.ht/~janus/web-server-racket-hello-
           | world/tree/... [3]: https://github.com/stefan-
           | hoeck/idris2-pack-db/blob/main/STA...
        
             | ants_everywhere wrote:
             | > (like Stackage) but still hosted on GitHub
             | 
             | I don't have much experience with Haskell, but one of the
             | worst experiences has been Stack's compile time dependency
             | on GitHub. GitHub rate limits you and builds take forever.
        
               | tome wrote:
               | That's interesting. Could you say more? This is something
               | that we (speaking as part of the Haskell community)
               | should fix. As far as I know Stack/Stackage should pick
               | up packages from Hackage. What does it use GitHub for?
        
               | ants_everywhere wrote:
               | I'm not entirely sure where it uses GitHub and where
               | Hackage, but there are a few GitHub issues on the Stack
               | repo about it:
               | 
               | - Binary upgrade of Stack fails due to GitHub API request
               | limit #4979
               | (https://github.com/commercialhaskell/stack/issues/4979)
               | 
               | - GitHub rate limiting can affect Stack CI #6034
               | (https://github.com/commercialhaskell/stack/issues/6034)
               | 
               | And a few more. The "fix" is having Stack impersonate the
               | user
               | (https://github.com/commercialhaskell/stack/pull/6036)
               | and authenticate to the API. This unblocks progress, but
               | this is really a design bug and not something I think
               | people should emulate.
               | 
               | Every other language I've used allows you to build code
               | without authenticating to a remote service.
        
             | orbifold wrote:
             | There are tasks, which are implemented as part of the
             | runtime and they appear to plan to integrate libuv in the
             | future. Some of the runtime seems to be fairly easy to hack
             | and have somewhat nice ways of interoperating with both C,
             | C++ and Rust.
        
           | agentultra wrote:
           | Lean _can_ be used to write software in [0]. I dare say that
           | it may even be the intended use for Lean 4. Work on porting
           | _mathlib_ to Lean 4 is far along and the mathematicians using
           | it will certainly continue to do so. However there is more
           | space for software written in Lean 4 as well.
           | 
           | However...
           | 
           | it's no where near ready for production use. They don't care
           | about maintaining backwards compatibility. They are more
           | focused on getting the language itself right than they are
           | about helping people build and maintain software written in
           | it. At least for the foreseeable future. If you do build
           | things in it you're working on shifting ground.
           | 
           | But it has a lot of potential. The C code generated by Lean 4
           | is good. Although, that's another trade-off: compiling to C
           | is another source of "quirks."
           | 
           | [0] https://agentultra.github.io/lean-4-hackers/
        
             | staunton wrote:
             | > Work on porting mathlib to Lean 4 is far along
             | 
             | As far as I understand, that work is in fact _done_.
        
           | mebassett wrote:
           | Lean definitely intends to be usable as a general purpose
           | language someday. but I think the bulk of the people involved
           | are more focused on automated theorem proving. The Lean FRO
           | [0] has funds to guide development of the language and they
           | are planning to carve out a niche for stuff that requires
           | formal verification. I'd say in terms of general purpose
           | programming it fits into the category of being "relatively
           | far from haskell in terms of maturity".
           | 
           | [0] https://lean-fro.org/about/roadmap-y2/
        
           | pmarreck wrote:
           | One reason I took interest in Idris (and lately Roc, although
           | it's even less mature) is the promise of a functional _but
           | usable to solve problems today_ language with all the latest
           | thinking on writing good code baked-in already, compiling to
           | a single binary (something I always envied about Go, although
           | unfortunately it 's Go). There simply isn't a lot there yet
           | in the space of "pure functional language with only immutable
           | values and compile time type checking that builds a single
           | fast binary (and has some neat developer-friendly
           | features/ideas such as dependent types, Roc's "tags" or
           | pattern-matching with destructuring)" (this rules out OCaml,
           | for example, despite it being mature). You get a lot of that,
           | but not all of it, with other options (OCaml, Elixir/Erlang,
           | Haskell... but those 3 offer a far larger library of ready-
           | to-import software at this point). Haskell did manage to
           | teach everyone who cares about these things that managing
           | side-effects and keeping track of "purity" is important.
           | 
           | But it's frankly still early-days and we're still far from
           | nirvana; Rust is starting to show some warts (despite still
           | being a massive improvement over C from a safety
           | perspective), and people are looking around for what's next.
           | 
           | One barely-touched thing is that there are compiler
           | optimizations made possible by pure functional/pure immutable
           | languages (such as caching a guaranteed result of an
           | operation where those guarantees simply can't be given
           | elsewhere) that have simply been impossible until now. (Roc
           | is trying to go there, from what I can tell, and I'm here for
           | it! Presumably, Rust has already, as long as you stick with
           | its functional constructs, which I hear is hard sometimes)
        
             | staunton wrote:
             | > Rust is starting to show some warts (despite still being
             | a massive improvement over C from a safety perspective)
             | 
             | The way it seems to me is that actually Rust aims to be an
             | improvement over C++ rather than C. (And Zig aims to be an
             | improvement over C rather than C++.)
             | 
             | The major downsides of both will be the same as their
             | reference point: Rust will eventually be too complicated
             | for anyone to understand while still not being really safe
             | (and the complexity then comes back to bite you one more
             | time). Zig will be easy to understand and use but too
             | unsafe to use for important applications (at least once
             | people start really caring about software in important
             | applications).
             | 
             | Both of these will be fairly niche because compiling to a
             | single binary just isn't as important, as elegant as it
             | might be.
        
           | bmitc wrote:
           | When I last looked into Lean, I was highly unimpressed, even
           | for doing math proofs. There's no way I'd invest into as a
           | general-purpose language.
           | 
           | Idris at least does state that they what people building real
           | programs with it and don't want it to just be a research
           | language.
           | 
           | For dependent types, I myself am skeptical about languages
           | trying to continuously push more and more stuff into types. I
           | am not certain that such efforts are a net positive on
           | writing good software. By their very definition, the more
           | typed a language gets, the less programs it can represents.
           | That obviously reduces buggy programs, but it also reduces
           | non-buggy programs that you can implement. Highly typed
           | languages force more and more effort into pre-compile time
           | and you will often find yourself trying to fit a problem into
           | the chains of the type system.
           | 
           | Rather, I think reasonably multi-paradigm languages like F#
           | are the sweet spot. Just enough strict typing and functional
           | core to get you going for most of your program, but then it
           | allows classes and imperative programming when those
           | paradigms are appropriate.
           | 
           | I think the way to go to write better software is better
           | tooling and ergonomics. I don't think type systems are going
           | to magically save us.
        
             | tsimionescu wrote:
             | > By their very definition, the more typed a language gets,
             | the less programs it can represents. That obviously reduces
             | buggy programs, but it also reduces non-buggy programs that
             | you can implement.
             | 
             | While I generally share your skepticism, I think this is
             | quite wrong. A good part of the point of advanced type
             | systems is to make more complex problems possible while
             | still being well typed. For example, in C, if you want a
             | function whose return type is tied to an input argument's
             | type, you either use void* and casts (no type safety), or
             | you don't write that function. In languages with even
             | slightly more advanced type systems, you can write that
             | function and still get full type safety.
             | 
             | Even more advanced type systems achieve the same things:
             | you can take programs that can only be written in a simpler
             | type system and make them safe. In standard Haskell, for
             | example, you can't write a Monad and actually have the
             | compiler check that it respects the Monad laws - the
             | implementation of Monad functions just assumes that any
             | type that implements the right shape of functions will work
             | as a monad. With dependent types, you can actually enforce
             | that functions designed to work with monads only apply to
             | types that actually respect the monad laws.
             | 
             | The trade-off with very complex type systems is different,
             | in my opinion: after some point, you start duplicating your
             | program's logic, once in the implentation code, but again
             | in the type signatures. For example, if you want to specify
             | that a sort function actually sorts the input list, you
             | might find that the type specification ends up not much
             | shorter than the actual code of the function. And apart
             | from raw effort, this means that your type specifications
             | start being large enough that they have their own bugs.
        
         | lemonwaterlime wrote:
         | > why would you restrict yourself to Haskell? It's not bleeding
         | edge any more.
         | 
         | I'm not using Haskell because it's bleeding edge.
         | 
         | I use it because it is advanced enough and practical enough.
         | It's at a good balanced spot now to do practical things while
         | tapping into some of the advances in programming language
         | theory.
         | 
         | The compiler and the build system have gotten a lot more stable
         | over the past several years. The libraries for most production-
         | type activities have gotten a lot more mature.
         | 
         | And I get all of the above plus strong type safety and
         | composability, which helps me maintain applications in a way
         | that I find satisfactory. For someone who aims to be pragmatic
         | with a hint of scholarliness, Haskell is great.
        
           | iso8859-1 wrote:
           | > The compiler and the build system have gotten a lot more
           | stable over the past several years.
           | 
           | GHC2021 promises backwards compatibility, but it includes
           | ill-specified extensions like ScopedTypeVariables.
           | TypeAbstractions were just added, and they do the same thing,
           | but differently.[0] It hasn't even been decided yet which
           | extensions are stable[1], yet GHC2021 still promises
           | compatibility in future compiler versions. So either, you'll
           | have GHC retain inferior semantics because of backwards
           | compatibility, or multiple ways of doing the same thing.
           | 
           | GHC2024 goes even further and includes extensions that are
           | even more unstable, like DataKinds.
           | 
           | Another sign of instability is the fact that GHC 9.4 is still
           | the recommended[2] release even though there are three newer
           | 'stable' GHCs. I don't know of other languages where the
           | recommendation is so far behind! GHC 9.4.1 is from Aug 2022.
           | 
           | It was the same situation with Cabal, it took forever to move
           | beyond Cabal 3.6 because the subsequent releases had bugs.[3]
           | 
           | [0]: https://serokell.io/blog/ghc-dependent-types-in-
           | haskell-3 [1]: https://github.com/ghc-proposals/ghc-
           | proposals/pull/669 [2]: https://github.com/haskell/ghcup-
           | metadata/issues/220 [3]: https://github.com/haskell/ghcup-
           | metadata/issues/40
        
         | gtf21 wrote:
         | > That's why, if you like the Haskell philosophy, why would you
         | restrict yourself to Haskell?
         | 
         | In the essay, I didn't say "Haskell is the only thing you
         | should use", what I said was:
         | 
         | > Many languages have bits of these features, but only a few
         | have all of them, and, of those languages (others include
         | Idris, Agda, and Lean), Haskell is the most mature, and
         | therefore has the largest ecosystem.
         | 
         | On this:
         | 
         | > It's not bleeding edge any more.
         | 
         | "Bleeding edge" is certainly not something I've used as a
         | benefit in this essay, so not really sure where this comes from
         | (unless you're not actually responding to the linked essay
         | itself, but rather to ... something else?).
        
         | giraffe_lady wrote:
         | > As the typed FP ecosystem is moving towards dependent typing
         | (Agda, Idris, Lean)
         | 
         | I'm not really sure where the borders of "the typed FP language
         | ecosystem" would be but feel pretty certain that such a thing
         | would enclose also F#, Haskell, and OCaml. Any one of which has
         | more users and more successful "public facing" projects than
         | the languages you mentioned combined. This is not a dig on
         | those languages, but they are niche languages even by the
         | standards of the niche we're talking about.
         | 
         | You could argue that they point to the future but I don't
         | seriously believe a trend among them represents a shift in the
         | main stream of functional programming.
        
           | xupybd wrote:
           | [delayed]
        
         | mightybyte wrote:
         | > That's why, if you like the Haskell philosophy, why would you
         | restrict yourself to Haskell? It's not bleeding edge any more.
         | 
         | Because it has a robust and mature ecosystem that is more
         | viable for mainstream commercial use than any of the other
         | "bleeding edge" languages.
        
         | js8 wrote:
         | > 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.
         | 
         | First of all, does ecosystem move to dependent types? I think
         | the practical value of Hindley-Milner is exactly in the fact
         | that there is a nice boundary between types and terms.
         | 
         | Second, why would type checking running indefinitely be a
         | practical problem? If I can't prove a theorem, I can't use it.
         | The program that doesn't typecheck in practical amount of time
         | is in practice identical to non-type-checked program, i.e. no
         | worse than a status quo.
        
           | DonaldPShimoda wrote:
           | No, the FP community at large is definitely not moving toward
           | dependent types. However, much more of the FP _research_
           | community is now focused on dependent types, but a good chunk
           | of that research is concerned with questions like  "How do we
           | make X benefit of dependent types work in a more limited
           | fashion for languages without a fully dependent type system?"
           | 
           | I think we'll continue to see lots of work in this direction
           | and, subsequently, a lot of more mainstream FP languages will
           | adopt features derived from dependent types research, but
           | it's not like everybody's going to be writing Agda or Coq or
           | Idris in 10 years instead of, like, OCaml and Haskell.
        
             | cubefox wrote:
             | I'm not even sure if any human is still writing code in 10
             | years.
        
         | tkz1312 wrote:
         | > 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.
         | 
         | The point is that programming in a pure language with typed
         | side effects and immutable data dramatically reduces the size
         | of the state space that must be reasoned about. This makes
         | programming significantly easier (especially over the long
         | term).
         | 
         | Of the languages that support this programming style Haskell
         | remains the one with the largest library ecosystem, most
         | comprehensive documentation, and most optimised compiler. I
         | love lean and use it professionally, but it is nowhere near the
         | usability of Haskell when it comes to being a production ready
         | general purpose language.
        
           | dataflow wrote:
           | > dramatically reduces the size of the state space that must
           | be reasoned about
           | 
           | True
           | 
           | > This makes programming significantly easier (especially
           | over the long term)
           | 
           | Not true. (As in, the implication is not true.)
           | 
           | There are many many factors that affect ease of programming
           | and the structure of the stage space is just one of them.
        
         | aSanchezStern wrote:
         | Uhh, endless recursion doesn't cause your typechecker to run
         | indefinitely; all recursion is sort of "endless" from a type
         | perspective, since the recursion only hits a base case based on
         | values. The problem with non-well-founded recursion like `main
         | = main` is that it prevents you from soundly using types as
         | propositions, since you can trivially inhabit any type.
        
       | watt wrote:
       | The 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 wrote:
         | It 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-N...
        
           | watt wrote:
           | well 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 wrote:
             | > 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
        
             | housecarpenter wrote:
             | Technically, `a :: Num` would be _declaring_ , or
             | _defining_ that `a` is of type `Num`. After you see `a ::
             | Num`, you can assume from then on as you 're reading the
             | program that `a` has type `Num`; if something is
             | incompatible with that assumption it will result in a
             | compiler error. This is different from `Num a`, which is
             | making the assertion that `a` is of type `Num`, but that
             | assertion may evaluate as true or false. It's similar to
             | how assignment is different from equality, so that most
             | programming languages with C-style syntax make a
             | distinction between `=` and `==`.
             | 
             | There's also the fact that `Num` is technically not a type,
             | but a type _class_ , which is like a level above a type:
             | values are organized into types, and types are organized
             | into classes. Though this is more of a limitation of
             | Haskell: conceptually, type classes are just the types of
             | types, but in practice, the way they're implemented means
             | they can't be treated in a uniform way with ordinary types.
             | 
             | So that's why there's a syntactic distinction between `Num
             | a` and `a :: Num`. As for why `Num` comes before `a`,
             | there's certainly a reasonable argument for making it come
             | after, given that we'd read it in English as "a is a Num".
             | I think the reason it comes before is that it's based on
             | the usual function call syntax, which is `f x` in Haskell
             | (similar to `f(x)` in C-style languages, but without
             | requiring the parentheses). `Num` is kind of like a
             | function you call on a type which returns a boolean.
        
         | desdenova wrote:
         | This terseness is what makes Haskell so hard to approach for
         | beginners, unfortunately.
         | 
         | After you went through the effort of learning the syntax, it
         | becomes very clear what it means, but I agree that dropping
         | punctuation between a bunch of names isn't the clearest
         | communication.
         | 
         | It becomes even worse when you start using third party
         | libraries that abuse Haskell's ability to define custom
         | operators, so you get entire APIs defined as arcane sigil
         | invocations instead of readable functions.
         | 
         | That's why I gave up the idea of using Haskell for actual
         | programming, and just took the functional programming
         | philosophy from it to other languages.
         | 
         | As for the meaning, in a more conventional (rust) syntax, it'd
         | be similar to this:                   fn add1<A: Num>(a: A) ->
         | A
        
           | ColonelPhantom wrote:
           | I disagree that the 'arcane sigil invocations' are
           | necessarily a problem. Yes, they can be, but I also think
           | they can definitely be preferable!
           | 
           | Naming everything as a function often leads to a problem of
           | very deep visual nesting. For example, map-then-filter can be
           | written as "filter p . map f" in Haskell, whereas in sigil-
           | free languages you'd write a mess like (lambda (x) (filter p
           | (map f x))) in Lisp or "lambda x: filter(p, map(f, x))" in
           | Python.
           | 
           | Of course, function composition is a very simple case, but
           | something like lenses are another less simple case where a
           | library would be unusable without custom operators.
        
             | kazinator wrote:
             | Right off the bat, the problem is that filter p . map f
             | looks like it wants to be filter, then map. Nearly all
             | modern languages that have pipelining, whereby nested
             | function calls are extraposed into a linear formm, go left
             | to right.
             | 
             | In the Lisp or Python, it is crystal clear that the entire
             | map expression is a constituent of the filter expression.
        
           | kazinator wrote:
           | The problem with A -> B -> C is that it could be (A -> B) ->
           | C or A -> (B -> C).
           | 
           | The -> operator in C is obvious. Though it does have left to
           | right associativity, it's largely moot because only A would
           | be a pointer-valued expression. The B and C would have to be
           | member names:                 A->left->value.
           | 
           | Even if the associativity went right to left, the semantic
           | interpretation would have to be that the left member of A is
           | selected, and then the value member of that.
           | 
           | When X -> Y means "mapping from X to Y", the associativity
           | could be anything, and make sense that way. Moreover (X -> Y)
           | -> Z is different from X -> (Y -> Z). One is a function which
           | maps an X-to-Y function to Z, whereas the other is a function
           | which maps X to a Y-to-Z function.
        
         | AzzieElbab wrote:
         | Yes, this bad mathematician's lingo is really unnecessary. It
         | means someone else wrote an implementation of an interface
         | called Num for your `a` type. Well, it is not really an
         | interface. The correct term is type class, but that is details.
        
         | gtf21 wrote:
         | If I may, as the author of "such sloppily constructed prose"
         | (which I think might be a little unfair as a summary of all
         | 6.5k words):
         | 
         | In this syntax note, I was not trying to teach someone to write
         | Haskell programmes, but rather to give them just enough to
         | understand the examples in the essay. I did test it on a couple
         | of friends to see if it gave them enough to read the examples
         | with, but was trying to balance the aim with not making this
         | section a complete explainer (which would have been too long).
         | 
         | Perhaps I got the balance wrong, which is fair enough, but I
         | don't think it's required to define _every single_ term
         | upfront. It's also not crucial to the rest of the essay, so
         | "The article lost me at following sentence" feels a bit
         | churlish.
        
         | kazinator wrote:
         | "Satisfies" is a common math term. For instance given
         | x + 3 = 4
         | 
         | the value of x which satisfies the equation is 1. To satisfy is
         | to be a value which makes true some truth valued formula (such
         | as an invocation of a predicate like blue(x) or equation like
         | the above, or inequality or such).
         | 
         | Satisfiability comes up in logic; a "SAT" problem is, in a
         | nutshell, the problem of finding the combination of true/false
         | values of a bunch of Boolean variables, which make some formula
         | true.
         | 
         | When the Haskeller says that "Num a" is something that is
         | satisfied by a, it means that it's a kind of predicate which
         | says _a_ is a _Num_. That predicate is true of an _a_ which is
         | a _Num_.
        
       | agentultra wrote:
       | It'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.
        
         | mumblemumble wrote:
         | It is. But I think that, for that purpose, I like F# even
         | better. Even beyond getting access to the .NET ecosystem, you
         | also get some language design decisions that were specifically
         | meant to make it easier to maintain large codebases that are
         | shared among developers with varying skill levels.
         | 
         | Lack of typeclasses is a good example. Interface inheritance
         | isn't my favorite, but after years working as the technical
         | lead on a Scala project I've been forced to concede that
         | haranguing people who just want to do their job and go home to
         | their family about how to use them properly isn't a good use of
         | anyone's time. Everyone comes out of school already knowing how
         | to use interfaces and parametric polymorphism, and that is
         | _fine_.
        
           | agentultra wrote:
           | I adore Scott Wlaschin's work [0] -- that's where I picked up
           | on the acronym, BLOBS! F# is super cool, I agree.
           | 
           | [0] https://www.youtube.com/watch?v=Up7LcbGZFuo
        
             | vips7L wrote:
             | His book Domain Driven Design Made Functional is really
             | good. It really opened my eyes on DDD.
        
               | tome wrote:
               | A book I find truly wonderful! If I was going to
               | recommend one book about how to design software, it would
               | be this one.
        
           | chefandy wrote:
           | Anecdotally, the handful of people I've known that worked in
           | commercial Haskell shops, after the initial honeymoon period
           | intensified by _actually finding a paying Haskell dev job_ ,
           | wishes they were using a more practical "happy medium" FP
           | language. I don't know anyone that's used F# in production,
           | but nobody I know that's worked in Elixir, erlang, or elm
           | environments has expressed the same frustration.
        
             | tome wrote:
             | Interesting. I wonder where you met them. I've worked with
             | tens of Haskell programmers in my career, most of whom were
             | sad if they were required to stop working in Haskell. I've
             | never met anyone who actively sought out a Haskell job and
             | then subsequently wanted to stop working in Haskell.
        
               | chefandy wrote:
               | Your sample size sounds much bigger than mine.
        
             | IWeldMelons wrote:
             | Jane Street famously uses Ocaml, which is, granred, not F#
             | but closee enough/
        
             | 1-more wrote:
             | Many of my colleagues would describe themselves as taking
             | pay cuts to write Haskell provisioned with Nix with type-
             | safe interop with Ruby and our frontend. If you're into it,
             | you're into it. And it has the effect of putting absolute
             | mutants on your team.
        
         | darby_nine wrote:
         | If 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 wrote:
           | Well 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 wrote:
             | > 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.
        
               | agentultra wrote:
               | I've heard of systems like Roc + Nea taking this to the
               | extreme [0]. Totally a way to go.
               | 
               | Haskell, to some extent, can help you structure your
               | program in this way where the business logic is just
               | simple, plain, old functional code. You can write the
               | data marshalling, logging, and networking layers
               | separately. There are a few ways to tackle that in
               | Haskell in varying levels of complexity as you would
               | expect coming from other general-purpose programming
               | languages.
               | 
               | [0] https://www.youtube.com/watch?v=zMRfCZo8eAc&t=952s
        
         | gavinray wrote:
         | Only on HN will you read someone unironically suggest writing
         | LOB software in Haskell.
        
           | tome wrote:
           | Why not? Many of us do it every day.
        
             | gavinray wrote:
             | Let's suppose that you and I are non-technical founders of
             | some medium-size software product.
             | 
             | If we were to rank the most important factors in choosing
             | how to build our product, I think we may be able to agree
             | that they're likely:
             | 
             | - The talent pool and availability of the language
             | 
             | - The ecosystem of libraries and ancillary tools like
             | monitoring/debugging/observability
             | 
             | - The speed-of-development vs cost-of-maintenance tradeoff
             | of the language
             | 
             | I will give Haskell that it can be rapidly written by those
             | proficient, and tends to have less bugs if it compiles than
             | many languages.
             | 
             | But for "what language is easy to employ and has an
             | expansive ecosystem + tooling", I feel like you have to
             | hand it to Java, .NET, Python, TypeScript, Go, etc...
        
               | tome wrote:
               | That's shifting the goalposts somewhat! Can Haskell be
               | used for LOB software. Yes! In fact it's the one I am
               | most effective in for that purpose. If I was starting a
               | startup, it would be in Haskell, no question. "Let's
               | suppose that you and I are non-technical founders of some
               | medium-size software product ..." Well, that's something
               | else entirely.
        
               | mchaver wrote:
               | I am unironically being paid to do it!
               | 
               | My experience is Haskell is one of those ecosystems that
               | has a greater talent pool than there are available
               | positions. I feel like cost of maintenance is pretty nice
               | because you have less bugs. You may have to roll up your
               | sleeves and get your hands dirty to update open source
               | libraries or make stuff that is missing, but code
               | reliability seems to be worth it.
        
               | rebeccaskinner wrote:
               | I think you're taking a particular view of things that
               | can work, but it's not the only correct view.
               | 
               | > The talent pool and availability of the language
               | 
               | There are certainly more Javascript or Python developers
               | out there than Haskell developers, but I think it's wrong
               | to imply that Haskell is a hard language to hire for.
               | There are more people out there who want to work with
               | Haskell than there are Haskell jobs, and picking Haskell
               | can be a really great way to recruit high quality talent.
               | It's also quite possible to train developers on Haskell.
               | A lot of companies hire people who don't have experience
               | with their particular language. The learning curve for
               | Haskell may be a bit steeper, but it's certainly
               | tractable if you are hiring people who are eager to
               | learn.
               | 
               | > The ecosystem of libraries and ancillary tools like
               | monitoring/debugging/observability
               | 
               | Other languages have _more_ of these, but it's not like
               | Haskell is missing basic ecosystem things. I actually
               | find that Haskell is pretty nice with this stuff overall.
               | It's not quite as automatic as what you might get with
               | running something in the JVM, but it's not that big of a
               | lift, and for a lot of teams the marginal extra effort
               | here is more than worth it because of the other benefits
               | you get from Haskell.
               | 
               | > The speed-of-development vs cost-of-maintenance
               | tradeoff of the language
               | 
               | Haskell is really excellent here in my experience. You
               | can write unmaintainable code in any language, but
               | Haskell gives you a lot of choice in how you build your
               | application, and it makes refactoring a lot nicer than in
               | any other language I've used. You don't get some of the
               | nice IDE features to rename things or move code around
               | automatically, but working in a large Haskell codebase
               | you really do start to see ways that the language makes
               | structural and architectural refactoring a lot easier.
               | 
               | > But for "what language is easy to employ and has an
               | expansive ecosystem + tooling", I feel like you have to
               | hand it to Java, .NET, Python, TypeScript, Go, etc...
               | 
               | Those are all perfectly good choices. I think what people
               | tend to overlook is that Haskell is _also_ a perfectly
               | good choice. Everything has tradeoffs, but Haskell isn't
               | some terrible esoteric choice that forces you to leave
               | everything practical on the table. It really is useful
               | day to day as a general purpose language.
        
           | jose_zap wrote:
           | We do that at our company, it's been great
        
           | bunderbunder wrote:
           | I am not prepared to hunt down the citation, but several
           | years back I stumbled across a paper that was trying to
           | compare the effectiveness of various languages for grinding
           | out "domain logic-y" code. Among the ones they evaluated,
           | Haskell came out on top in terms of both time to get the work
           | done and correctness of the implementation.
           | 
           | IIRC this was testing with students, which would be both a
           | strength and a weakness of the experimental design.
        
           | __MatrixMan__ wrote:
           | Not just HN, Cardano's smart contract language, Plutus, is
           | based on Haskell.
        
         | ninetyninenine wrote:
         | Haskell is just hard when you get to the advanced stuff. I mean
         | beyond monads there's the state monad, lenses, etc. a lot of
         | these are not trivial to wrap your brain around. Like for Java
         | head first design patterns I read it and I'm good. For monads
         | it took me weeks to wrap my head around it and I still don't
         | understand every monad.
         | 
         | Yeah I get a bunch of basic apps can be modeled easily, you get
         | unparalleled static safety but programmers will spend an
         | inordinate amount of time figuring out mind bending algebraic
         | patterns.
         | 
         | I think something like ocaml or f sharp are more down to earth.
        
           | agentultra wrote:
           | The advanced parts of most languages can get hairy. Don't
           | mistake familiarity with complexity. Even Java has hard,
           | dense code that is difficult to work with and learn.
           | 
           | I tend to stay away from the advanced parts of Haskell when
           | writing BLOBS.
           | 
           | The advanced stuff is there when you need to write libraries
           | that need generic code that works with types you haven't
           | defined yourself. You learn it as you go when you need to.
           | 
           | But when I'm writing BLOBS I mostly stick to using libraries
           | and that's pretty straight-forward.
        
       | wredue wrote:
       | Good 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 wrote:
       | I 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.
        
       | igouy wrote:
       | Awaiting part 2 -- Why not to use Haskell?
        
       | sesm wrote:
       | Haskell 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 wrote:
         | There's a strong case that laziness should be the default:
         | 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 wrote:
         | Laziness 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.)
        
         | iainmerrick wrote:
         | Right, I was surprised I had to scroll down here so far to see
         | the first mention of laziness; it's _the_ core feature of
         | Haskell (copied from Miranda so researchers had a non-
         | proprietary language to build their work on).
         | 
         | From everything I've ready about people's experiences with
         | using Haskell for large projects, it sounds like lazy
         | evaluation unfortunately adds more problems than it removes.
        
         | whateveracct wrote:
         | > This experiment clearly shows, that laziness on language
         | level is a bad idea.
         | 
         | This is quite the claim. I know plenty of experienced and
         | productive Haskellers who disagree with this (myself included)
        
       | semiinfinitely wrote:
       | Haskell had a large impact on the design of JAX which is probably
       | the future of ML development frameworks.
        
       | drdrey wrote:
       | The 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)
        
       | jesse__ wrote:
       | The 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 wrote:
         | This 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 [2]
         | https://ucsd-progsys.github.io/liquidhaskell/ [3]
         | https://hackage.haskell.org/package/stm [4]
         | https://hackage.haskell.org/package/gdp [5]
         | https://serokell.io/blog/introduction-to-template-haskell [6]
         | https://hackage.haskell.org/package/regexqq [7]
         | https://hackage.haskell.org/package/lens [8]
         | https://hackage.haskell.org/package/async [9]
         | https://hackage.haskell.org/package/effectful
        
       | semiinfinitely wrote:
       | I love Haskell but I hate using it.
        
       | jes5199 wrote:
       | in 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.
        
       | me_vinayakakv wrote:
       | I 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 wrote:
         | In 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.
        
         | jose_zap wrote:
         | Yes, it is possible to linearize it. You can, for example use
         | do notation:                   result <- do             a <-
         | someEitherValue             b <- anotherEitherValue
         | return (doStuff a b)
         | 
         | In the above example the do notation will unwrap the values as
         | an and b, but if one of the results is Left, the computation is
         | aborted, returning the Left value.
         | 
         | This is one just of the many techniques available to make error
         | checking linear.
        
         | TJSomething wrote:
         | I've been out of the Scala game for a few years, but I would
         | use a for comprehension with the cats EitherT monad
         | transformer.
         | 
         | https://typelevel.org/cats/datatypes/eithert.html
         | def divisionProgramAsync(inputA: String, inputB: String):
         | EitherT[Future, String, Double] =           for {             a
         | <- EitherT(parseDoubleAsync(inputA))             b <-
         | EitherT(parseDoubleAsync(inputB))             result <-
         | EitherT(divideAsync(a, b))           } yield result
        
           | valenterry wrote:
           | Yeah, but Usually we just use something like ZIO nowadays. So
           | the code becomes:                   def
           | divisionProgramAsync(inputA: String, inputB: String):
           | IO[String, Double] =           for {             a      <-
           | parseDoubleAsync(inputA)             b      <-
           | parseDoubleAsync(inputB)             result <- divideAsync(a,
           | b)           } yield result
           | 
           | (the annoying wrapping/unwrapping isn't necessary with ZIO
           | here)
           | 
           | You can also write this shorter if you want:
           | def divisionProgramAsync(inputA: String, inputB: String):
           | IO[String, Double] =           for {             (a, b) <-
           | parseDoubleAsync(inputA) <*> parseDoubleAsync(inputB)
           | result <- divideAsync(a, b)           } yield result
        
       | 1oooqooq wrote:
       | What's one big web facing application which handles modern
       | authentication protocols?
        
         | whateveracct wrote:
         | mercury.com ?
        
       | nazka wrote:
       | Is 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 wrote:
         | This 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.
        
       | skybrian wrote:
       | As 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 wrote:
         | It'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 wrote:
           | Yes, 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.
        
       | adamddev1 wrote:
       | I want Haskell with the tooling, DX, and packages of TypeScript.
        
       | hugodan wrote:
       | I'll tell you why, it is simply the best refactoring experience
       | out there. This.
        
       ___________________________________________________________________
       (page generated 2024-09-12 23:00 UTC)