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…
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.
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
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
- PureScript’s other backends (e.g.
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)
- PureScript’s tooling and IDE experience is superior to Haskell’s. To install PureScript, one runs
npm i purescript spago parceland 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
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. 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.
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.
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
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 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
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.
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
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?
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
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)
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:)
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!