Leveraging 70% of the web: a PHP backend for PureScript

Hello everyone! :wave:

I’d like to share an experimental project I’ve been working on recently: phpurs, a brand new backend that transpiles PureScript directly to modern PHP syntax (thanks CoreFn).

Why PHP? While the broader JS ecosystem has heavily leaned towards TypeScript, the PHP world (which still powers roughly 70% of the web) has somewhat been left without a true, strictly-typed functional alternative. Furthermore, a massive portion of the web relies on legacy shared hosting or containerless VPS environments where escaping the PHP runtime is either impossible, financially unviable, or technically difficult. Just a few examples: cPanel/Plesk, GoDaddy, Bluehost, SiteGround, standard HostGator/OVH shared plans, basic LAMP stacks…

Recently, we’ve observed a genuine surge of interest for strict typing and functional concepts within the PHP community (with recent PHP versions and RFCs: short closures, match expressions, pipe operators and partial application), although the frustration that comes with its current limitations is just as real. So… phpurs aims to provide a “comfort tool” for any PHP developer looking to finally take the leap towards a much safer paradigm. It opens a door for those who want the elegance of a purely functional language like PureScript, completely bypassing the JS/TS ecosystem, while keeping deployment as simple as an FTP upload of .php files. A massive win for legacy or small projects, which constitute the majority of the web today.

For the PureScript ecosystem, I think this is a strategic opportunity to gain visibility and traction by tapping directly into the largest demographic of web developers in the world.

To prove the viability of the concept, I built a universal multi-runtime pedagogical benchmark (altbak.pub), which runs identical PureScript code across Node.js, Arista ES, Chez Scheme, Erlang BEAM… and now natively on PHP. You can find the benchmark here.

This project is still in its infancy. It is very much a proof-of-concept, the codebase is currently quite messy, highly experimental, and heavily AI-copiloted.

If anyone is interested in parsing CoreFn, optimizing PHP code generation (e.g., proper tail call optimizations for mutual recursion, inlining), or just exploring how to bridge pure FP with the PHP runtime, your help would be obviously welcome!

You can find the project here.

Love :heart:

Edit (6th of July): the initial benchmark gave 1m30s. After dozens of compiler optimizations, the tests are now successful after ~2s. Getting better and better!

4 Likes

Interesting! Consider flake/ nixifying it for determinism’s sake. I can help if you want.

1 Like

Yes. I think once the compiler is optimized, we’ll be able to look into that. And I’d be happy to have your help if you’d like to assist me. I’ll keep you posted!

News:

The initial benchmark gave 1m30s. After dozens of compiler optimizations, the tests are now successful after a run of ~1.7s. That is a great step forward.

The benchmarks I’ve seen often (online) show Node (V8) to be 2 to 5 times faster than PHP 8, for this kind of task. Our benchmark seems to reflect that reality fairly accurately. The compiler has therefore likely reached its peak performance area.

In our context, the x3 difference (only x2 compared to purerl) is not a problem:

  • We’re targeting a segment of the web that, while certainly the majority, is also focused on factors other than pure performance (presence, visibility, lower costs, etc.). For advanced projects (e.g. containerized stack, full DevOps control…), JS will often remain a better compiler backend (or even: Go, Erlang…).
  • In the vast majority of cases, the main bottleneck remains I/O. As a result, the difference in performance isn’t even noticeable.

Support for Aff has also been added, backed by the Revolt event loop and PHP 8.1+ Fibers. The behavior of the event loop in the compiled PHP code perfectly mirrors its Node.js counterpart: asynchronous operations are handled transparently without blocking the OS thread, and the main process will automatically wait for all pending Aff tasks to complete before exiting.

In parallel, the project structure has been fully reorganized to align with the standard conventions of other alternate backends (like purerl, purescm, or purescript-go). We now have:

  • A dedicated FFI ecosystem based on standard forks (prelude here, effect here, aff here, etc.).
  • A standalone compiler repository.
  • A ready-to-use starter template to easily bootstrap new projects.

And of course, the README has been updated to make onboarding easy for any developer who wants to compile their PureScript project into PHP.

So… The compiler itself is now at a very advanced stage in terms of features and performance. My next immediate priorities are to clean up and refactor the codebase to make it truly maintainable, and then move on to the next phase: improving the DX (e.g. npm install) and building a real-world POC to battle-test the implementation.

I’ll keep you posted!

1 Like

News #2:

For ~76k lines of .purs code here, the compiler went from 42 min to 2.6 seconds for a full cold build, and down to an average of ~0.6s for incremental builds. A no-op build check now takes 0.16 seconds.

Fast localized development is now a reality for phpurs :rocket:

Cheers :wave:

1 Like

News #3:

Important step today. It’s finally getting battle-tested! :tada:

I’ve been successfully running phpurs against the test suite of a real-world project (b8x).

In the GIF below, you can see the developer experience in action. I’m seamlessly switching from the Node.js backend to the PHP backend, triggering a new build after the backend container restarts, and running the entire test suite. As simple as that.

Kapture 2026-07-08 at 00.32.29

One really interesting takeaway: while raw micro-benchmarks might sometimes show a 3x performance gap between Node and PHP, the difference is actually much narrower on a real project. Here, the test suite runs in ~1.5s on PHP vs ~1.3s on Node (which is only a ~1.15x difference).

Next step: Putting the compiler backend up against the official PureScript passing tests.

That’s quite good.

1 Like

News #4:

Numerous improvements: a package set for easy setup, optional bundling (with dead code elimination) for each entrypoint (e.g. Main.purs), additional rounds of compiler optimization, performance improvements for the compiled code (sometimes faster than Node now!), with huge gains for Aff, Free, Run

Here, for example, is a bundled file uploaded to a legacy WordPress site. No installation was needed. Just uploaded the file via FTP to the client’s VPS, on an independent and low-cost web hosting service (quite frequent for small websites). Easy as pie. :white_check_mark:

And as I explained here, Aff is compiled into a native Revolt userland event loop, which handles Fibers, without requiring custom C extensions or complex server-side configurations (e.g. Swoole).

Still on passing tests, in parallel.

:wave:

News #5:

Oh my… I didn’t know this project existed! I just leveraged it to cut the phpurs codebase by more than 50%.

By plugging phpurs into Arista’s backend-agnostic purescript-backend-optimizer, I was able to drop the custom Dead Code Elimination (DCE) and a lot of optimization tricks (uncurry, monads into imperative code, etc.) I had manually built. The optimizer handles everything beautifully at the CoreFn level. The compiler is now drastically simpler and focused purely on translating a lean, pre-optimized CoreFn into modern PHP. No need to reinvent the wheel (even if I liked it, I could have saved a week, LOL).

Incremental compilation & The ES backend trick:

The phpurs compiler itself is now compiled using purs-backend-es. The final output files are still in JS, with seamless usage by Spago.

In phpurs, just like in purs-backend-es, we use purescript-backend-optimizer to transform your project into a highly efficient internal AST (which can be seen as a super-optimized corefn-like representation). Because the phpurs executable itself is compiled via purs-backend-es, this in-memory AST is represented under the hood as plain intermediary JavaScript objects (instead of complex ES6 classes). Then, to translate these objects into the final code, we essentially imitate how purs-backend-es processes projects, except we simply swap its standard ES/JS file dump phase with a PHP one. This means phpurs only adapts the end of the compiling chain to meet specific PHP runtime prerequisites.

And since these intermediary JavaScript objects are perfectly isomorphic to native JSON, we can effortlessly dump them to disk (.phpurs-cache.json) to cache them between runs! No need to recompile them into PHP if they haven’t changed. The optimizer doesn’t support incremental compilation out of the box yet, so I created a fork with a hook to skip unchanged modules (I’ll propose a PR to upstream soon), during the final phase (.php files creation).

Note that phpurs is still independent and is not a fork of purs-backend-es. It only leverages its imported inner utils (purescript-backend-optimizer). Just like purescm, it seems to me.

As a bonus, the ES backend made the compiler about 30% faster overall.

In other words: phpurs is simpler, better, and still incremental. Even though performance could still be improved (purescript-backend-optimizer could also be incremental), I think that, in terms of performance, things are probably at their best now.

:wave:

1 Like

News #6:

It’s one thing to switch from one backend to another in a series of partial and pure tests. It’s quite another to do so in a full-fledged product with numerous dependencies and effects (Aff, S3, Postgres, RabbitMQ, HTTP, etc.). That’s now been done and proven. :white_check_mark:

The POC has been updated. Example of async PHP FFI here.

N.b. For HTTP (API), there’s no need for FrankenPHP or anything like it to handle all requests.

News #7:

Big big milestone today! phpurs is no longer experimental :tada:

I’m thrilled to announce that the official PureScript passing tests are now all green against the phpurs backend! :green_circle:

Technically, a tiny handful (about 5 tests related to the new one-line Haskell-like deriving feature) are currently skipped because I haven’t aligned the compiler with v0.16+ just yet (still on v0.15), but that will be published very soon.

With this huge step forward, I consider phpurs to be officially graduating from its “experimental POC” phase. It is now a reliable, robust compiler backend that is ready for production use.

In fact, I’m already using it in production myself. For several client projects, the hosting environments are strictly PHP-only (standard shared hosting or restricted VPS), yet I’m able to write the entire application purely in PureScript, compile it, and just FTP the output. The developer experience is fantastic, and the runtime stability is rock solid. As a bonus: the perfs have never been better. All that work on the compiler has paid off.

If any of you want to give it a spin, please don’t hesitate! I’d especially love to see community contributions, whether it’s PRs for the compiler itself or, even more importantly, adding missing PHP FFIs to core and major PureScript libraries so we can support larger projects out of the box. I will be more than happy to review and merge them.

I’ve written about that here.

Let’s bring the elegance of PureScript to the 70% of the web that still runs on PHP! :elephant:

Love :heart:

@harryprayiv, keeping you posted: the compiler has now reached production maturity (it’s optimized and the tests are green) and is no longer just a POC. If you’re still up for helping me nixify it so the dev environment is deterministic, I’d be absolutely pleased to welcome your PR on the compiler repo! Feel free, when you have some time. Nothing urgent. Cheers :wave: