TL;DR - Imagine you write the code in Python, and in CI an LLM tool rewrites it to Go, and re-exposes the Python API as Python package released with Go equivalent of maturin.
- You get the speed of Go, but you develop your code in Python.
- The users of your package still use it as Python package.
- The LLM automatically does the conversion.
- The LLM also writes tests to ensure that the conversion is correct.
I’d like to explore this in maybe 3-6 months time, once I’m done with the work around my other project, django-components. So just wanted to post this here and let it ferment.
If you find this interesting and would like to join me, leave me a message. Or even if you think this could never work. Also, ideas on how this could be funded are welcome.
Background
I’m freelance developer (here’s more on me) and for the past 1.5 year I’m also co-maintainer / developer of django-components - a frontend framework for Django.
In the last couple of months I was exploring how I could get funding for our project, which forced me to examine how our project, Django, Python web development fits the overall web development scene.
There’s this saying that “On the Internet, the winner takes all”. JavaScript web dev ecosystem (React, Vue, etc) is larger, more mature, and IMO it overall feels much faster to go from zero to completed project. So a frontend framework for Python and specifically Django feels like a niche that can have hard time being funded. But I’d like to continue working on django-components, and get it to v1…
So had to ask myself - What could be a unique take that would make django-components competetive (or at least interesting) again other established frontend frameworks?
At the same time, I’m someone who always wants my packages / code to be faster and better. This even led me to off-load some parts of django-components to Rust with maturin. And frankly, the ease of use with which one can build Rust-Python hybrid packages with maturin / PyO3 is incredible.
(There may be similar tools like maturin in other programming languages, I haven’t explored that.)
Modern JS frontend frameworks often rely on purely client-side solution - The frontend code is pre-compiled into static HTML / JS / CSS assets. On the other hand, with non-JS web frameworks, the UI has to be rendered / prepared on the server.
But there is also server-side rendering (SSR) for JS frontend frameworks like NextJS. And maybe, if we compiled our Python code to Rust, and packaged it with maturin, it could be faster than NextJS?
With current advances to LLMs, the conversion from Python to Rust could be possibly automated. Imagine you write the code in Python, and in CI a tool rewrites it to Rust, and re-exposes the Python API as Python package released with maturin. LLMs are NOT great at generating new knowledge, but they are good at transforming same info into different formats, so this could work.
While the idea sounds good, I learnt that the reality is that SSR is only a fraction of infra cost for large websites. I also benchmarked various scenarios, and having a Go/Rust server with AlpineJS for client-side interactivity was only about ~20-30% faster than Nuxt (Vue) and ~50% faster than Next (React). And the setup with client-side Vue and React + REST server was surprisingly fast even if they have to send subsequent requests to the server to fetch page data (here data hydration with Alpine was the limiting step for Go+Alpin setup).
(I tested “time to interactivity” - how long it takes to load the page, wait for 1000 items to load, and wait until the framework (Vue/React/Alpine) responds to me clicking on the last item.)
Overall, this approach was interesting, but it wasn’t yielding a performance boost that would justify migrating from Vue or React to Python for web development.
However, that made me think - what if, instead of focusing only on web development, we try to apply this to the Python ecosystem overall?
Proposal
Taking this idea more seriously, there are a few changes and thoughts:
- Instead of Rust, transpile Python to Go. Go and Rust are similar perf-wise. But Go is closer to Python in syntax and features - Go has garbage collection like Python does, and Rust’s borrow checker and lifetimes would be a pain to make work well.
- For exposing Go to Python, there’s gopy. It’s not as polished as maturin, but gets the job done for a proof of concept.
- Transpiling organisation’s Python business logic code doesn’t make much sense, if that logic simply calls slow Python libraries (e.g. Django). Rather, it should be open source packages that should be transpiled to Go first. Only after all project dependencies have their Go equivalents, would it make sense for orgs to convert also their code.
- If a package (e.g. Django) is transpiled to Go, how would the organisation’s business code access the transpiled open source package? We’d need to have a package registry like PyPI, that would store the Go-transpiled copies of the packages that are otherwise available on the PyPI.
- Versioning - This also means that each different version that’s on PyPI would have a corresponding Go copy.
- Each Python project converted to Go would need to store that Go code in a (publicly available?) source control (e.g. Github). That way, when Python package A depends on Python package B, then A’s Go equivalent could directly point to B’s Go equivalent.
- This would need to be online service / SaaS, since this would rely on LLMs for transpilation, database for storing the Py→ Go relationships and entries, and for hosting the converted packages so they can be downloaded.
- The Py→ Go conversion would be part of the package’s build step. E.g. by collecting the source distribution, we can get a directory with files that we know need to be transpiled.
- Testing - To ensure that the Go code is correct, the LLM would be prompted to write tests for the 1) initial Python code (if missing), and then 2) rewrite the same tests in Go.
- Cost - Hard to say. In my proof of concept, even a minimal project (2-3 files, ~3 small functions) the whole flow took about 3-4 minutes of LLM compute (don’t know how much tokens that was). But obviously I didn’t try to optimize anything.
- Edge cases - Packages that rely on magical behaviour (dunder methods) could be harder to transpile.
The above is not an exhaustive list, I’m sure there’s a lot more nuances. So I’m curious to hear other’s thoughts.
Why going through the hussle of transpiling from Python to Go? I still need to finish my proof of concept. But I hope/expect that this could lead to at least 10x perf boost, despite the need to convert between Go/Python objects on the interface of the Go-compiled packages. And I expect that that’s much better than what we can expect from gradual improvements to Python runtimes in the next few years (tho I might be wrong).
Also to be clear, I’m not advocating against working on CPython and similar. I just think that in the age of LLMs, this might be an effective way of making Python code more performant.