When to use Haskell?

It seems that even the staunchest PureScript fans still prefer Haskell for a lot of tasks. The PureScript compiler is written in Haskell. I’ve heard a presentation where a team started with PureScript for some backend processing task and then migrated to Haskell. I’m wondering, what sort of tasks is Haskell better at than PureScript? I get an overall impression that PureScript is better for frontend and Haskell for backend, but I don’t really know what PureScript is missing for backend jobs, since the libraries available seem to cover a pretty broad spectrum of backend tasks…

5 Likes

I prefer PureScript’s libraries and the small language differences from Haskell seem to me to be in PureScript’s favor (especially, and i think everyone agrees on this, records). But if i wanted to build deployable binaries, using threading and/or heavy parallelism or Template Haskell-ish things or if i wanted to use libraries that aren’t available in PureScript or have an FFI with C i would certainly use Haskell.

The differences are sufficiently small that for most purposes it’s your toolchain that will be the big issue for a team moving in either direction.

My $0.02, only. Interested to see what others opinions are.

7 Likes

AFAIK, Phil started PureScript because he couldn’t write Haskell in the browser. Alternative options had too many tradeoffs that weren’t worth it.

Haskell definitely has better out-of-box support for backend things. PureScript doesn’t yet have a mature ecosystem of libraries for backend-related things. For example, consider the Haskell web framework, yesod, coupled with persistent+esquelito. You get a powerful web server framework with database integration that works pretty well and makes it easier to do database migration via Template Haskell. This can be easily created via a Stack template and has a book documenting how to use it. PureScript could provide similar support (though I’m not sure whether Template PureScript will ever be a thing), but it’s not as well tested/refined. While people are working to improve that, it’s still a work in progress.

The main arguments against using PureScript on the backend

  • Node.js is a much less reliable runtime than using Haskell’s runtime system where green threads and asynchronous exceptions are first-class citizens.
  • Haskell is faster than Node.js AFAIK. Moreover, Haskell has more optimizations/inliners than PureScript at the moment.
  • Haskell has a more mature ecosystem for such things. PureScript could in the future, but more work needs to be done
  • Haskell can use a full range of various types (e.g. unsigned/signed integers) whereas PureScript’s types are limited to Int and Number AFAIK
  • PureScript’s other backends (e.g. go, c) which remove some of these limitations aren’t as mature. It would be easier to develop for them if non-JS compilers got better support in the language repo (a future goal AFAIK, but development isn’t there yet)
  • Haskell is lazy whereas PureScript is strict. Not necessarily a con, but sometimes it makes writing code much easier.

The main arguments for using PureScript on the backend

  • better interop between the server and client (e.g. shared types, shared serialization)
  • some companies aren’t willing to go full Haskell. A PureScript-ified JavaScript on the backend allows them to switch back to JavaScript if things go south (e.g. lose a developer to a better job, team tries it and just doesn’t like it, etc.)
  • PureScript’s tooling and IDE experience is superior to Haskell’s. To install PureScript, one runs npm i purescript spago parcel and IDE-related tools. These all work out-of-box without issue. To install Haskell and its necessary tooling, one usually spends a few hours figuring out how to get it working until their tweaks work for them. However, what worked for them might not work for others
  • Haskell has a lot of baggage that PureScript learned from and avoided (e.g. record syntax, though this is being fixed in the upcoming Haskell release; a less granular type class; a weird numeric type class hierarchy)
  • Haskell is lazy whereas PureScript is strict. Not necessarily a con, but it is another thing to get used to / learn.

Note: also my $0.02

7 Likes

To the PS benefits I would add:

  • Huge JS ecosystem with many, many libraries which are well tested and mature and used in production by many people.

  • Simple FFI structure.

  • Ease of integration with third party APIs because it is quite usual that you are going to get ready to use clients for Typescript or plain JS.

  • SSR rendering using the same templates in the case of any react based UI library (we are using react-basic with SSR).

  • JS tools for diagnosing performance problems, deployment etc.

  • Know patterns how to deploy and scale node applications + ease of deployments nearly everywhere where JS is present.

But most important to me is the language itself its tooling and… ecosystem. It is true for sure that PS ecosystem is not as mature as Haskell one but it is nice and clean. It is enough to check purescript-contrib, much better prelude in my opinion (no partial functions - partiality checking out of the box is also a benefit of PS), clean and tiny purescript-node bindings. Also you don’t have to be worried that every new library is an another set of “language extensions” which you have to learn in order to understand what is going on. I’m not a haskeller but I think that we can say that nearly nobody uses just standard Haskell really.
I also count JS ecosystem as a part of the additional PureScript environment here because I don’t think that binding to some well tested libraries from JS world is a bad thing. I think that we should just appreciate the users scale (I’m mean “testers” again) here and benefit from this backend ecosystem.

Regarding language features Record and row types in general make also a huge difference. They give the power to express extensibility in a clean way (no ugly Has* constraints) and are wonderful tools to modularize the code. Things like Variant and checked exceptions (purescript-checked-exceptions) or effects system (purescript-run) or just lightweight Record type which comes with multiple instances for free are the most prominent examples.

Node.js is a much less reliable runtime than using Haskell’s runtime system where green threads and asynchronous exceptions are first-class citizens.

@JordanMartinez Could you please elaborate a bit more why do you think that nodejs is unreliable? I think that it is hard to find server side runtime which has larger user groups (read “testers”) and industry investment at the moment but maybe my estimation is just plain wrong. I’m not sure how big Haskell user group is in comparison to that.

Haskell is faster than Node.js AFAIK. Moreover, Haskell has more optimizations/inliners than PureScript at the moment.

This is also really interesting statement. Do you think that Node.js / V8 is not optimized well with its JIT etc.? Let me rephrase it: I like to look at web apps from the perspective which @afc sketched above. Usually on the backend we have a tiny layer (where I hope even purescript-run with a few “high level” binds will work nicely) and application is IO bound (read “db bound”). If we have any CPU or CPU / IO bounded computations (like image/video processing, heavy db queries etc.) we usually move them to the async queue and to a separate separate process / machine or langauge / tool or… to the client itself! Given that perspective / and probably my use case I think that nodejs performance is excellent but I have not deployed anything really large yet.
It would be really nice if we could somehow compare if nodejs is really so slow and if we really have to wait for any other backend… and an base ecosystem (like aff) for this backend… and for http, sql, redis binding libs for this backend… etc. etc. etc. :wink: It is also important to think about V8 JIT in this context and how our extensible records are going to behave on this new faster alternate backend.

If you are interested in backend development @Woody has started recently a community driven purescript-node-contrib and is working on this stuff extensively.

P.S.
We are starting to experiment with run based approach in our purescript-webrow experiment at work. We want to build something like Python Django but in PS. It is still pre apha but we have form validation (bidirectional) toolkit in place and some most important elements of HTTP flow. Now we want to sketch testing framework, next is tighter selda integration and finally we want to provide example integration with material ui templating (using purescript-react-basic-mui).
Beside of the above we have a few internal microservices based on httpure and we have two larger services during development but we want to finish them with webrow when we reach its first release.

6 Likes

I want to add to this point that it is (in theory) possible to have “perfect serialization” model for interop in PS. In my opinion it could be another selling point for PS on both sides.
Currently it is not “perfect” because not all PS types are represented as a proper JSON underneath. Sums and products are not proper JSON underneath (we could have something similar to Variant and Array here).
If we could control this piece of codegen somehow we could get zero cost (read “zero allocation” / “zero code”) serialization and deserialization with just consistency checking… But this probably won’t happen.
@garyb already warned me :wink: that we should treat underlining JS representation for types as an implementation detail and not rely on it… but maybe we should reconsider “a small compilation option” for purs :stuck_out_tongue:

2 Likes

SSR rendering using the same templates in the case of any react based UI library (we are using react-basic with SSR ).

  • Huge JS ecosystem with many, many libraries which are well tested and mature and used in production by many people.
  • Simple FFI structure.
  • Ease of integration with third party APIs because it is quite usual that you are going to get ready to use clients for Typescript or plain JS.

Good points. Thanks for highlighting those.

Also you don’t have to be worried that every new library is an another set of “language extensions” which you have to learn in order to understand what is going on. I’m not a haskeller but I think that we can say that nearly nobody uses just standard Haskell really.

It’s true that nobody uses standard Haskell. However, I find that understanding the language extensions is more of a 1-time cost. The funny thing is that you’re already familiar with a number of them. PureScript designed their syntax in some situations as though one had written Haskell and then turned on specific language extensions.

Could you please elaborate a bit more why do you think that nodejs is unreliable?

I should have clarified that that was something Harry said once when I was asking a similar question. I think he found that there were too many possible surprises that one wouldn’t find in Haskell. I’m not sure how much of that is based on opinion and how much is based on fact. So, take it with a grain of salt.

I think that nodejs performance is excellent but I have not deployed anything really large yet.

There’s a difference between “X is faster than Y” and “Y is ‘fast enough’ for our purposes.” You could always look at the web server framework benchmarks for more info. Haskell would use warp. You could look at Node’s express for comparison. As always, take benchmarks with a grain of salt.

I think a better question might be, “When should we use Rust instead of other alternatives?” If you’ve gone through the learning curve of FP languages, getting used to Rust’s borrow-checker is something one is likely willing to learn, too.

3 Likes

I totally agree. I should have written: “PS on nodejs performance seems to be OK in the case our last small project when we compared it to the other (long standing in production) python django project both serving simple JSON APIs.”
Of course we can get completely different results when we add heavy SSR with react or complicated sql queries generation or purescript-run. We will see :wink:

It is intersting to check where in these benchmarks are really popular ruby or python frameworks and how much it is related to their widespread use and popularity and large scale success stories (like for example github which is written in a really slow rails or sentry which is implemented in really slow django).

So for me personally the most important questions are: Am I really going to have performance problems on the app layer? How funny or how hard is to setup, devel, maintain and test my new projects?

1 Like

This is a great point and in my experience of working on Haskell backends and tools (fulltime for ~4y now) setting up CI properly is still something that escapes me (and I’m kind of a DevOps person): it’s really easy to end up with 1h+ CI times. One of the solutions is to use Nix, but that requires buy-in and adds a whole lot of learning complexity.
If someone has a different experience and thinks I’m overexaggerating, please help me fix the terrible build times in Spago’s CI :slightly_smiling_face:

In comparison CI builds in PureScript are really easy and really quick. Yes, Node is not as performant as the Haskell runtime in absolute terms, but if you don’t need insane performance (read: as fast as C) then removing dev/operational complexity might be well worth it.

I did start many backend projects in Haskell in the past, but these days I’d totally just use PureScript for anything really (as usual, YMMV)

7 Likes

I spent like 10+ hours trying to get haskell-ide-server to work on 2 different distors and I still didn’t manage to get it to play nicely with vscode:)

Note: I did get it to install on nixos but then vscode just threw absolutely random errors which are nowhere to be found online…

Even tho the purescript ide tooling is still not as good as other enterprise-backed languages (eg: ts & f#) at least it’s super easy to install, something I really grew to appreciate lately:)

4 Likes

Wow, thanks for all the guidance everyone! Yeah we use PureScript in our frontend in production, but F# in the backend, and I’m constantly fighting the ecosystem to get effect tracking and get rid of partial functions. And I’ve only built toy-sized projects in Haskell. From what I’ve seen of PureScript, I love it and it sure looks mature for a lot of backend stuff. I can see though if you need super high-performance or you need to micromanage your threading model then Haskell is probably the better choice. It’s good to hear that if high-performance isn’t really a concern that PureScript is a viable option for backend!

3 Likes

I also count JS ecosystem as a part of the additional PureScript environment here because I don’t think that binding to some well tested libraries from JS world is a bad thing. I think that we should just appreciate the users scale (I’m mean “testers” again) here and benefit from this backend ecosystem.

:100:. We have been insanely productive with PS on the backend side for writing business-logic heavy microservices with the node backend. Anything we might want to do was always supported by a library of some kind and it just worked. To be honest, you’ll probably have written the PureScript FFI by the time the Haskell dependency compiles. Then, you also get the advantage that JS libraries tend to actually have documentation and examples.
I think a good reason to use Haskell is when working on the PureScript compiler.

7 Likes

I’ve written just as many Node backends as I have Haskell and I prefer to write them in Haskell which is easy since I wrote the Node backends in JavaScript. But even if I wrote them in PureScript, which clearly would’ve been a huge improvement, I’d rather not have to worry about having to use only 1 version of node per server since it’s globally installed.

With Haskell, I can run multiple Haskell programs running on different versions of the compiler and sets of libraries on the same server, within reason. With node, all of my servers running on the same server must be the same version of node. However, if you run each server in Docker, then this isn’t a concern.

Also, Javascript’s runtime will never truly compete with Haskell for performance. However, not all applications need the performance.

But, having said of this and taking into consideration all of the other comments, typically, the decision is based on your own personal competence in the language. How well you know Haskell is a paramount issue to consider.

I’d say generally speaking, Haskell on the backend is better than PureScript for most situations. So, if your goal is to get the best solution, then I’d suggest you work on getting comfortable with Haskell. If you are not interested in working in multiple languages, then PureScript on the backend will most likely work for you.

Bottom line is that there is no single answer here. It all depends on what you value, whether it be performance, flexibility or team skillset and which of these you can change.

3 Likes

Just adding info what options you have for multiple version of the node on the same server.

  • docker
  • nvm (node version manager)
  • nix
1 Like

All nvm does is change the GLOBAL version of node, you still only have 1 version at a time (unless they’ve changed something since I last used it).

I am somewhat Nix ignorant so take this with a grain of salt, but I believe you are referring to Nix Shell in particular.

1 Like

I believe you can specify node version on project basis with .nvmrc -> https://github.com/nvm-sh/nvm#nvmrc

I am referring to

which was taken from https://nixos.org/.

Both nix and nixos doesn’t have problem with different version of nodejs or any other package.

2 Likes

Looks like they did add stuff to nvm. Pretty cool actually.

Nice info on Nix. I’m going to learn it as soon I get the time.

2 Likes