[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)