1
Fork 0

Start working on lunarbox article

This commit is contained in:
prescientmoon 2025-06-11 07:18:54 +02:00
commit 7f55203625
Signed by: prescientmoon
SSH key fingerprint: SHA256:WFp/cO76nbarETAoQcQXuV+0h7XJsEsOCI0UsyPIy6U
2 changed files with 106 additions and 0 deletions
content/echoes/updating-lunarbox

Binary file not shown.

After

(image error) Size: 97 KiB

View file

@ -0,0 +1,106 @@
{ role=config }
``` =toml
created_at = "2025-06-10 02:10:42+02:00"
draft = true
```
::: description
My journey updating a half a decade old project of mine to use modern tooling, and some reflection on the evolution of the PureScript ecosystem. The final section is a sort of love letter to PureScript, and everything the language has meant to me.
:::
# Refactoring my moon in a box
[Lunarbox]: https://github.com/lunarcast/lunarbox
[Lunarbox API]: https://github.com/lunarcast/lunarbox-api
[Lunarbox][] is a functional programming language I build in my spare time around 2020. Waow, it's been half a decade already, I'm getting old, huh? Most of the project was written in [PureScript][], although the [canvas renderer][Lunarbox canvas renderer] has at some point been rewritten to [TypeScript][] for performance reasons. The [API][Lunarbox API] has been built by my friend [BlueGhost][], who also chose to use TypeScript.
[Lunarbox canvas renderer]: https://github.com/lunarcast/lunarbox/blob/develop/src/typescript/render.ts
[PureScript]: https://www.purescript.org/
[TypeScript]: https://www.typescriptlang.org/
[BlueGhost]: https://github.com/BlueGhostGH/
{ src="factorial.png" alt="Implementation of the factorial function in Lunarbox" width="75%" }
::: image-figure
A recursive [factorial][Factorial] implementation in Lunarbox. Try it out [here][Lunarbox factorial].
:::
On [2022-10-28]{ role=date } [Heroku][], a server hosting service commonly recommended to beginners, [removed their free tier][Heroku removed their free tier]. As you might've guessed, Heroku is precisely where Lunarbox (in particular, the aforementioned API) was being hosted. I was just starting university when said date rolled around, so I did very little about it. I asked my friend to back-up the [Postgres][PostgreSQL] database, before swiftly forgetting about the topic.
Lunarbox' deployment was quite a mess. The front-end required a fair bit of tooling to build, and an (by that time) outdated version of PureScript. Moreover, due to a lack of proper base URL handling (i.e. the project would always assume it lived at the base of an URL' path), Lunarbox' front-end was being hosted on [Netlify][] instead of something like [GitHub pages][] (which I'm a bit more familiar with). To make things worse, the back-end required a full on Postgres database, which made running it locally a bit annoying (I hate having to spin up Postgres databases locally aaaaaaaaaaa).
The "sane" thing to do might've been to try and set up a [Docker][] container and jump ship to some new hosting service offering a free tier, although that felt (to me) like asking for the cycle to repeat itself once a few years had passed by (ok, having a Docker container would've definitely helped make the process easier, but I don't particularly like Docker, alright?). I had already been planning to start self hosting things for a good while, and a low-risk project like Lunarbox would've surely been the perfect opportunity to do so, rigghttttt?
Three years have since passed, I'm almost done with my bachelor studies, my personal server's up and running, hosting dozens of services, yet Lunarbox is still dead.
[Factorial]: https://en.wikipedia.org/wiki/Factorial
[Lunarbox factorial]: about:blank
[Heroku]: https://www.heroku.com/
[Heroku removed their free tier]: https://help.heroku.com/RSBRUH58/removal-of-heroku-free-product-plans-faq
[PostgreSQL]: https://www.postgresql.org/
[Netlify]: https://www.netlify.com/
[GitHub pages]: https://pages.github.com/
[Docker]: https://www.docker.com/
Since I don't have that much professional experience, the people I've previously interviewed for have asked me to show them a personal project of mine. Twice now, Lunarbox is the project I've chosen to show, and something that's impressed both of them. Since I'm (yet again) looking for a summer job, I felt like now was the perfect time to try and get Lunarbox up and running again. In the years since Lunarbox' inception, I've become a super big fan of [Nixos][], so the path forwards was clear. I had to:
1. Update Lunarbox to modern versions of the tooling involved (the tedious part)
2. Get rid of the Postgres dependency, switching to something leaner like [SQLite][] (the annoying part)
3. Package up every component using Nix, and write a Nixos module for easy deployment (the fun part)
4. Reap the rewards, by deploying Lunarbox to my personal server! (the easy part)
[Nixos]: https://nixos.org/
[SQLite]: https://sqlite.org/
::: toc
:::
## Running the server
## Running the front-end
### Language changes
### Formless
### Reflection on the evolution of PureScript
## Nix packaging
## Writing a Nixos module
## Deploying
## Reflection on Lunarbox
Before going further, I wanted to take a step back and reflect on Lunarbox as a whole. For one, it introduced me to Functional Programming as a whole, and indirectly helped me land my first two jobs. Lunarbox started as a [fp-ts][] project. At first, I tried implementing type inference off the top of my head. Not only did I try that, I tried doing it directly on the node graph, which made it very strange for me to work on (at the time, at least). Defeated, I looked up some tutorials.
At first, I came across [a TypeScript implementation][Type inference for beginners] of [Hindley-Milner type inference][]. I haven't read the series since, but I remember it leaving quite a few gaps in my understanding at the time. Looking for more, I then came across another post, this time written in [Haskell][]. I sadly can't seem to find the exact post, but I think it was pretty similar to [this one][Implementing HM in Haskell]. One interesting thing here is the use of the [RWST monad transformer][The RWST monad transformer]. At the time, I (for some reason) thought it would be a good idea to try to do the same in Typescript via the monad transformer support offered by fp-ts ([example][StateT in fp-ts]). Unlike Haskell, TypeScript does not support any sort of typeclasses/traits/etc, which makes working with monad transformers quite painful — the programmer needs to pass around the typeclass dictionaries manually. Moreover, the lack of general do-notation made using monads painful even once they've been constructed. Nevertheless, not knowing what I was doing, I gave up for the time being, and decided to learn a proper functional programming language.
[fp-ts]: https://gcanti.github.io/fp-ts/
[Type inference for beginners]: https://medium.com/@dhruvrajvanshi/type-inference-for-beginners-part-1-3e0a5be98a4b
[Hindley-Milner type inference]: https://en.wikipedia.org/wiki/Hindley%E2%80%93Milner_type_system
[Haskell]: https://www.haskell.org/
[Implementing HM in Haskell]: https://blog.stimsina.com/post/implementing-a-hindley-milner-type-system-part-1
[The RWST monad transformer]: https://hackage.haskell.org/package/mtl-2.3.1/docs/Control-Monad-RWS-CPS.html#t:RWST
[StateT in fp-ts]: https://gcanti.github.io/fp-ts/modules/StateT.ts.html
My journey started with [Elm][]. To this day, I believe the official Elm tutorial is a great first step into functional programming. I remember building some very basic things (like a basic [TODO app][Elm TODO app]), which made me quickly realise that Elm's architecture was quite limiting. Having since worked with Elm professionally, I can't fathom how true my first impressions were. God, I hate Elm for non-trivial codebases. Performing every single effect via message-passing gets old very quickly, trust me. The values are not the only immutable things when using Elm — so is the codebase given complicated enough interactions between the underlying effects.
[Elm]: https://elm-lang.org/
[Elm TODO app]: https://git.moonythm.dev/prescientmoon/solar-conflux/src/branch/master/elm/todolist
Nonetheless, I then set my eyes on [F#][]. I remember doing [a few days][Advent of Code 2019 (day 3) in F#] of [Advent of Code][], and even spending a bit of time trying to write a [Yu-Gi-Oh! engine][Unfinished F# Yu-Gi-Oh! engine] (which I of course gave up on pretty quickly, given how gargantuan an undertaking that is). I actually liked F# quite a bit. I think this is the first time I learnt what monads actually were (I think I read about them in some F# library's documentation). I eventually gave up the language after facing some issues with whatever tool I was using for running F# in the browser (I honestly can't remember what said issues were, but oh well).
[F#]: https://fsharp.org/
[Advent of Code]: https://adventofcode.com/
[Advent of Code 2019 (day 3) in F#]: https://github.com/prescientmoon/aoc/blob/main/2019/days/3/Program.fs
[Unfinished F# Yu-Gi-Oh! engine]: https://git.moonythm.dev/prescientmoon/solar-conflux/src/branch/master/fsharp/ygosim
Finally, I landed on PureScript. The very same friend who would later end up writing Lunarbox' API told me about Lunarbox' existence a while before, but I think I had ignored it the instant I'd seen the language using [Bower][] as its package manager. Nonetheless, the new and shiny [Spago][] pulled me in. Having taken the slow steps to get there, the language itself felt quite easy to learn (although the ecosystem took me considerably longer to get used to).
[Bower]: https://bower.io/
[Spago]: https://github.com/purescript/spago
was my first foray into the world of type system programming and type theory as a whole. The